@aiden-ade/sandbox-agent 0.1.67 → 0.1.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +795 -65
  2. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -22087,7 +22087,7 @@ function describeError(error2) {
22087
22087
  }
22088
22088
 
22089
22089
  // src/version.ts
22090
- var AGENT_VERSION = "0.1.67";
22090
+ var AGENT_VERSION = "0.1.69";
22091
22091
 
22092
22092
  // src/daemon-worktree.ts
22093
22093
  var import_node_child_process3 = require("child_process");
@@ -23508,16 +23508,128 @@ async function collectClaudeLimits() {
23508
23508
  ].filter((window2) => Boolean(window2));
23509
23509
  })().catch(() => []), 4e3, []);
23510
23510
  }
23511
+ function antigravityWindowMinutes(window2) {
23512
+ if (window2 === "5h")
23513
+ return 5 * 60;
23514
+ if (window2 === "weekly")
23515
+ return 7 * 24 * 60;
23516
+ return null;
23517
+ }
23518
+ function antigravityGroupLabel(groupName) {
23519
+ if (typeof groupName !== "string" || !groupName.trim())
23520
+ return "Antigravity";
23521
+ return groupName.replace(/\s*models?$/i, "").trim() || groupName.trim();
23522
+ }
23523
+ function antigravityWindow(groupLabel, raw) {
23524
+ if (!raw)
23525
+ return null;
23526
+ const remainingFraction = numericValue(raw.remaining_fraction);
23527
+ if (remainingFraction === null)
23528
+ return null;
23529
+ const windowMinutes = antigravityWindowMinutes(raw.window);
23530
+ const resetAt = typeof raw.reset_time === "string" && raw.reset_time.trim() ? raw.reset_time : null;
23531
+ const windowShortLabel = limitLabel(windowMinutes, typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "Limit");
23532
+ const remaining = Math.max(0, Math.min(100, remainingFraction * 100));
23533
+ return {
23534
+ label: windowShortLabel,
23535
+ group: groupLabel,
23536
+ remaining,
23537
+ limit: 100,
23538
+ usedPercent: Math.max(0, Math.min(100, 100 - remaining)),
23539
+ ...windowMinutes !== null ? { windowMinutes } : {},
23540
+ ...resetAt ? { resetAt } : {},
23541
+ source: "antigravity-cli-json"
23542
+ };
23543
+ }
23544
+ function runOneShotCli(command, args, env, timeoutMs) {
23545
+ return new Promise((resolve14) => {
23546
+ const child = (0, import_node_child_process4.spawn)(command, args, { env, stdio: ["ignore", "pipe", "ignore"] });
23547
+ let stdout = "";
23548
+ let settled = false;
23549
+ const settle = (value2) => {
23550
+ if (settled)
23551
+ return;
23552
+ settled = true;
23553
+ clearTimeout(timer);
23554
+ resolve14(value2);
23555
+ };
23556
+ const timer = setTimeout(() => {
23557
+ if (!child.killed)
23558
+ child.kill();
23559
+ settle(null);
23560
+ }, timeoutMs);
23561
+ child.stdout.on("data", (chunk) => {
23562
+ stdout += chunk.toString("utf8");
23563
+ });
23564
+ child.on("error", () => settle(null));
23565
+ child.on("close", () => settle(stdout));
23566
+ });
23567
+ }
23568
+ async function collectAntigravityLimits(env) {
23569
+ const runtimeEnv = augmentCliPath2(env);
23570
+ const agyCommand = resolveExecutable("agy", runtimeEnv, "ALAN_ANTIGRAVITY_PATH");
23571
+ if (!agyCommand)
23572
+ return [];
23573
+ const stdout = await runOneShotCli(agyCommand, ["-p", "/usage", "--output-format", "json"], runtimeEnv, 15e3);
23574
+ if (stdout === null)
23575
+ return [];
23576
+ try {
23577
+ const parsed = JSON.parse(stdout);
23578
+ const groups = parsed.command?.data?.groups;
23579
+ if (!Array.isArray(groups))
23580
+ return [];
23581
+ return groups.flatMap((group) => {
23582
+ const groupLabel = antigravityGroupLabel(group.name);
23583
+ const buckets = Array.isArray(group.buckets) ? group.buckets : [];
23584
+ return buckets.map((bucket) => antigravityWindow(groupLabel, bucket)).filter((window2) => Boolean(window2));
23585
+ });
23586
+ } catch {
23587
+ return [];
23588
+ }
23589
+ }
23590
+ var OPENCODE_TOTAL_COST_PATTERN = /Total Cost\s+\$([\d,]+\.\d{2})/;
23591
+ function parseOpenCodeTotalCost(stdout) {
23592
+ const match = OPENCODE_TOTAL_COST_PATTERN.exec(stdout);
23593
+ if (!match)
23594
+ return null;
23595
+ return {
23596
+ label: "Spend",
23597
+ summary: `$${match[1]} all-time`,
23598
+ source: "opencode-cli-stats"
23599
+ };
23600
+ }
23601
+ async function collectOpenCodeLimits(env) {
23602
+ const runtimeEnv = augmentCliPath2(env);
23603
+ const opencodeCommand = resolveExecutable("opencode", runtimeEnv, "ALAN_OPENCODE_PATH");
23604
+ if (!opencodeCommand)
23605
+ return [];
23606
+ const stdout = await runOneShotCli(opencodeCommand, ["stats"], runtimeEnv, 4e3);
23607
+ if (stdout === null)
23608
+ return [];
23609
+ const window2 = parseOpenCodeTotalCost(stdout);
23610
+ return window2 ? [window2] : [];
23611
+ }
23511
23612
  async function collectLocalAgentProviderLimits(env = process.env) {
23512
23613
  if (cachedLimits && Object.keys(cachedLimits.value).length > 0 && Date.now() - cachedLimits.collectedAt < CACHE_TTL_MS) {
23513
23614
  return cachedLimits.value;
23514
23615
  }
23515
- const [codex, claude] = await Promise.all([collectCodexLimits(env), collectClaudeLimits()]);
23616
+ const [codex, claude, antigravity, opencode] = await Promise.all([
23617
+ collectCodexLimits(env),
23618
+ collectClaudeLimits(),
23619
+ collectAntigravityLimits(env),
23620
+ collectOpenCodeLimits(env)
23621
+ ]);
23516
23622
  const limits = {};
23517
23623
  if (codex.length > 0)
23518
23624
  limits.codex_app_server = codex;
23519
23625
  if (claude.length > 0)
23520
23626
  limits.claude_cli = claude;
23627
+ if (antigravity.length > 0)
23628
+ limits.antigravity_cli = antigravity;
23629
+ if (opencode.length > 0) {
23630
+ limits.opencode_cli = opencode;
23631
+ limits.opencode_serve = opencode;
23632
+ }
23521
23633
  if (Object.keys(limits).length > 0) {
23522
23634
  cachedLimits = { value: limits, collectedAt: Date.now() };
23523
23635
  } else {
@@ -32313,12 +32425,30 @@ function extractText(parsed) {
32313
32425
  return "";
32314
32426
  }
32315
32427
  function extractToolInput(parsed) {
32316
- return parsed.parameters ?? parsed.input ?? parsed.args ?? parsed.arguments ?? {};
32428
+ const rawInput = parsed.parameters ?? parsed.input ?? parsed.args ?? parsed.arguments ?? {};
32429
+ if (typeof rawInput === "object" && rawInput !== null && !Array.isArray(rawInput) && ("Arguments" in rawInput || "arguments" in rawInput)) {
32430
+ const record2 = rawInput;
32431
+ const nested = record2.Arguments ?? record2.arguments;
32432
+ if (typeof nested === "object" && nested !== null) {
32433
+ return nested;
32434
+ }
32435
+ }
32436
+ return rawInput;
32317
32437
  }
32318
32438
  function extractToolName(parsed) {
32319
32439
  for (const key of ["tool_name", "toolName", "name"]) {
32320
32440
  const value2 = parsed[key];
32321
- if (typeof value2 === "string" && value2.trim()) return value2;
32441
+ if (typeof value2 === "string" && value2.trim()) {
32442
+ const trimmed = value2.trim();
32443
+ if (trimmed === "call_mcp_tool" || trimmed === "callMcpTool") {
32444
+ const input = parsed.parameters ?? parsed.input ?? parsed.args ?? parsed.arguments ?? {};
32445
+ const server2 = typeof input.ServerName === "string" ? input.ServerName.trim() : typeof input.serverName === "string" ? input.serverName.trim() : "";
32446
+ const tool = typeof input.ToolName === "string" ? input.ToolName.trim() : typeof input.toolName === "string" ? input.toolName.trim() : "";
32447
+ if (server2 && tool) return `mcp__${server2}__${tool}`;
32448
+ if (tool) return tool;
32449
+ }
32450
+ return trimmed;
32451
+ }
32322
32452
  }
32323
32453
  const server = typeof parsed.server === "string" ? parsed.server.trim() : "";
32324
32454
  const method = typeof parsed.method === "string" ? parsed.method.trim() : "";
@@ -32427,17 +32557,14 @@ function syncAntigravitySubagentTranscript(transcriptPath, parentToolUseId, pres
32427
32557
  const toolCalls = Array.isArray(entry.tool_calls) ? entry.tool_calls : [];
32428
32558
  toolCalls.forEach((rawToolCall, toolIndex) => {
32429
32559
  const toolCall = asRecord(rawToolCall);
32430
- const toolName = toolCall && stringField2(toolCall, "name");
32560
+ if (!toolCall) return;
32561
+ const toolName = extractToolName(toolCall);
32431
32562
  if (!toolName) return;
32563
+ const toolInput = extractToolInput(toolCall);
32432
32564
  const toolId = antigravityTranscriptToolId(parentToolUseId, lineIndex, toolIndex);
32433
32565
  pendingToolIds.push(toolId);
32434
32566
  emittedEventCount += 1;
32435
- void presenter.onToolUse(
32436
- toolName,
32437
- toolCall?.args ?? toolCall?.parameters ?? {},
32438
- toolId,
32439
- parentToolUseId
32440
- );
32567
+ void presenter.onToolUse(toolName, toolInput, toolId, parentToolUseId);
32441
32568
  });
32442
32569
  const content = stringField2(entry, "content");
32443
32570
  if (content && isAntigravityToolResultEntry(entry)) {
@@ -35042,7 +35169,7 @@ function handleCopilotStructuredEvent(parsed, context, state) {
35042
35169
  case "tool.execution_complete": {
35043
35170
  const toolId = typeof data.toolCallId === "string" ? data.toolCallId : `copilot-tool-${Date.now()}`;
35044
35171
  const result = typeof data.result === "object" && data.result !== null ? data.result : {};
35045
- const content = typeof result.content === "string" ? result.content : typeof result.detailedContent === "string" ? result.detailedContent : typeof data.error === "string" ? data.error : "";
35172
+ const content = result.codeContext !== void 0 || result.structuredContent !== void 0 || result.content !== void 0 && Array.isArray(result.content) ? JSON.stringify(result, null, 2) : typeof result.content === "string" ? result.content : typeof result.detailedContent === "string" ? result.detailedContent : typeof data.error === "string" ? data.error : "";
35046
35173
  const success2 = typeof data.success === "boolean" ? data.success : true;
35047
35174
  const trackedLauncher = state.activeBackgroundTaskIds?.has(toolId) === true;
35048
35175
  const linkedBackground = state.backgroundTaskIdsByToolId?.has(toolId) === true;
@@ -35253,6 +35380,9 @@ function buildCursorToolResultText(result) {
35253
35380
  const resultRecord = result;
35254
35381
  const success2 = typeof resultRecord.success === "object" && resultRecord.success !== null ? resultRecord.success : null;
35255
35382
  if (success2) {
35383
+ if (success2.codeContext !== void 0 || success2.structuredContent !== void 0 || success2.content !== void 0 && Array.isArray(success2.content)) {
35384
+ return JSON.stringify(success2, null, 2);
35385
+ }
35256
35386
  if (typeof success2.content === "string" && success2.content.trim().length > 0)
35257
35387
  return success2.content;
35258
35388
  if (typeof success2.output === "string" && success2.output.trim().length > 0)
@@ -43094,11 +43224,11 @@ var SocketWithoutUpgrade = class _SocketWithoutUpgrade extends Emitter {
43094
43224
  */
43095
43225
  _resetPingTimeout() {
43096
43226
  this.clearTimeoutFn(this._pingTimeoutTimer);
43097
- const delay2 = this._pingInterval + this._pingTimeout;
43098
- this._pingTimeoutTime = Date.now() + delay2;
43227
+ const delay3 = this._pingInterval + this._pingTimeout;
43228
+ this._pingTimeoutTime = Date.now() + delay3;
43099
43229
  this._pingTimeoutTimer = this.setTimeoutFn(() => {
43100
43230
  this._onClose("ping timeout");
43101
- }, delay2);
43231
+ }, delay3);
43102
43232
  if (this.opts.autoUnref) {
43103
43233
  this._pingTimeoutTimer.unref();
43104
43234
  }
@@ -45079,8 +45209,8 @@ var Manager = class extends Emitter {
45079
45209
  this.emitReserved("reconnect_failed");
45080
45210
  this._reconnecting = false;
45081
45211
  } else {
45082
- const delay2 = this.backoff.duration();
45083
- debug10("will wait %dms before reconnect attempt", delay2);
45212
+ const delay3 = this.backoff.duration();
45213
+ debug10("will wait %dms before reconnect attempt", delay3);
45084
45214
  this._reconnecting = true;
45085
45215
  const timer = this.setTimeoutFn(() => {
45086
45216
  if (self.skipReconnect)
@@ -45100,7 +45230,7 @@ var Manager = class extends Emitter {
45100
45230
  self.onreconnect();
45101
45231
  }
45102
45232
  });
45103
- }, delay2);
45233
+ }, delay3);
45104
45234
  if (this.opts.autoUnref) {
45105
45235
  timer.unref();
45106
45236
  }
@@ -61929,13 +62059,13 @@ var StreamableHTTPClientTransport = class {
61929
62059
  this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
61930
62060
  return;
61931
62061
  }
61932
- const delay2 = this._getNextReconnectionDelay(attemptCount);
62062
+ const delay3 = this._getNextReconnectionDelay(attemptCount);
61933
62063
  this._reconnectionTimeout = setTimeout(() => {
61934
62064
  this._startOrAuthSse(options).catch((error2) => {
61935
62065
  this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error2 instanceof Error ? error2.message : String(error2)}`));
61936
62066
  this._scheduleReconnection(options, attemptCount + 1);
61937
62067
  });
61938
- }, delay2);
62068
+ }, delay3);
61939
62069
  }
61940
62070
  _handleSseStream(stream, options, isReconnectable) {
61941
62071
  if (!stream) {
@@ -64748,38 +64878,103 @@ var publishSkillInputSchema = defineWireSchema()(external_exports.object({
64748
64878
  expectedContentHash: sha256Schema,
64749
64879
  uploadGrant: runtimeUploadGrantSchema
64750
64880
  }));
64751
- var boundedMetadataSchema = external_exports.record(external_exports.string().max(1024)).default({}).superRefine((value2, context) => {
64752
- const entries = Object.entries(value2);
64753
- if (entries.length > 64) {
64754
- context.addIssue({
64755
- code: external_exports.ZodIssueCode.custom,
64756
- message: "metadata has more than 64 entries"
64757
- });
64758
- }
64759
- for (const [key] of entries) {
64760
- if (new TextEncoder().encode(key).byteLength > 128) {
64761
- context.addIssue({
64762
- code: external_exports.ZodIssueCode.custom,
64763
- message: `metadata key exceeds 128 UTF-8 bytes: ${key.slice(0, 32)}`
64764
- });
64881
+ var skillFrontmatterAdmissionSchema = external_exports.object({
64882
+ name: external_exports.string().min(1).max(64).regex(SKILL_NAME_PATTERN),
64883
+ description: external_exports.string().trim().min(1).max(1024)
64884
+ }).passthrough();
64885
+ var PROJECTED_VALUE_MAX_BYTES = 16384;
64886
+ function formatKeys(keys) {
64887
+ const shown = keys.slice(0, 3).map((key) => `metadata.${key}`);
64888
+ const remaining = keys.length - shown.length;
64889
+ const listed = shown.join(", ");
64890
+ return remaining > 0 ? `${listed} and ${remaining} more` : listed;
64891
+ }
64892
+ function unsupported(message) {
64893
+ return { severity: "warning", code: "UNSUPPORTED_FRONTMATTER", message, path: "SKILL.md" };
64894
+ }
64895
+ function projectSkillFrontmatter(admitted) {
64896
+ const conformance = [];
64897
+ const raw = admitted;
64898
+ const optionalText = (key, max) => {
64899
+ const value2 = raw[key];
64900
+ if (value2 === void 0)
64901
+ return void 0;
64902
+ if (typeof value2 !== "string") {
64903
+ conformance.push(unsupported(`${key} is not a string; the Agent Skills spec expects text`));
64904
+ return void 0;
64905
+ }
64906
+ const trimmed = value2.trim();
64907
+ if (!trimmed)
64908
+ return void 0;
64909
+ if (trimmed.length > max) {
64910
+ conformance.push(unsupported(`${key} exceeds ${max} characters and was not indexed`));
64911
+ return void 0;
64912
+ }
64913
+ return trimmed;
64914
+ };
64915
+ const optionalToolList = (key, max) => {
64916
+ const value2 = raw[key];
64917
+ if (Array.isArray(value2) && value2.every((entry) => typeof entry === "string")) {
64918
+ raw[key] = value2.join(", ");
64919
+ }
64920
+ return optionalText(key, max);
64921
+ };
64922
+ const license = optionalText("license", 1024);
64923
+ const compatibility = optionalText("compatibility", 500);
64924
+ const allowedTools = optionalToolList("allowed-tools", 1024);
64925
+ const metadata = {};
64926
+ const rawMetadata = raw.metadata;
64927
+ if (rawMetadata !== void 0) {
64928
+ if (typeof rawMetadata !== "object" || rawMetadata === null || Array.isArray(rawMetadata)) {
64929
+ conformance.push(unsupported("metadata is not a mapping and was not indexed"));
64930
+ } else {
64931
+ const encoder = new TextEncoder();
64932
+ const notStrings = [];
64933
+ const selfReferential = [];
64934
+ const tooLarge = [];
64935
+ for (const [key, value2] of Object.entries(rawMetadata)) {
64936
+ if (typeof value2 !== "string")
64937
+ notStrings.push(key);
64938
+ let encoded;
64939
+ if (typeof value2 === "string") {
64940
+ encoded = value2;
64941
+ } else {
64942
+ try {
64943
+ encoded = JSON.stringify(value2) ?? "";
64944
+ } catch {
64945
+ selfReferential.push(key);
64946
+ continue;
64947
+ }
64948
+ }
64949
+ if (encoder.encode(encoded).byteLength > PROJECTED_VALUE_MAX_BYTES) {
64950
+ tooLarge.push(key);
64951
+ continue;
64952
+ }
64953
+ metadata[key] = value2;
64954
+ }
64955
+ if (notStrings.length > 0) {
64956
+ conformance.push(unsupported(`${formatKeys(notStrings)} ${notStrings.length === 1 ? "is" : "are"} not ${notStrings.length === 1 ? "a string" : "strings"}; the Agent Skills spec expects text`));
64957
+ }
64958
+ if (selfReferential.length > 0) {
64959
+ conformance.push(unsupported(`${formatKeys(selfReferential)} ${selfReferential.length === 1 ? "refers" : "refer"} to itself and ${selfReferential.length === 1 ? "was" : "were"} not indexed`));
64960
+ }
64961
+ if (tooLarge.length > 0) {
64962
+ conformance.push(unsupported(`${formatKeys(tooLarge)} ${tooLarge.length === 1 ? "is" : "are"} too large to index`));
64963
+ }
64765
64964
  }
64766
64965
  }
64767
- });
64768
- var skillFrontmatterSchema = external_exports.object({
64769
- name: external_exports.string().min(1).max(64).regex(SKILL_NAME_PATTERN),
64770
- description: external_exports.string().trim().min(1).max(1024),
64771
- license: external_exports.string().trim().min(1).max(1024).optional(),
64772
- compatibility: external_exports.string().trim().min(1).max(500).optional(),
64773
- metadata: boundedMetadataSchema,
64774
- "allowed-tools": external_exports.string().trim().min(1).max(1024).optional()
64775
- }).passthrough().transform((value2) => ({
64776
- name: value2.name,
64777
- description: value2.description,
64778
- ...value2.license ? { license: value2.license } : {},
64779
- ...value2.compatibility ? { compatibility: value2.compatibility } : {},
64780
- metadata: value2.metadata,
64781
- ...value2["allowed-tools"] ? { allowedTools: value2["allowed-tools"] } : {}
64782
- }));
64966
+ return {
64967
+ metadata: {
64968
+ name: admitted.name,
64969
+ description: admitted.description,
64970
+ ...license ? { license } : {},
64971
+ ...compatibility ? { compatibility } : {},
64972
+ metadata,
64973
+ ...allowedTools ? { allowedTools } : {}
64974
+ },
64975
+ conformance
64976
+ };
64977
+ }
64783
64978
  var skillPackageDiagnosticSchema = external_exports.object({
64784
64979
  severity: external_exports.literal("warning"),
64785
64980
  code: external_exports.enum(["BROKEN_REFERENCE", "UNSUPPORTED_FRONTMATTER"]),
@@ -65052,7 +65247,10 @@ async function inspectSkillPackage(packageRoot, options = {}) {
65052
65247
  inspectedFiles.sort(comparePackageFiles);
65053
65248
  const parsedMetadata = parseSkillMetadata(inspectedFiles.find((file2) => file2.manifest.path === "SKILL.md")?.bytes, options.expectedName === void 0 ? (0, import_node_path15.basename)(root) : options.expectedName);
65054
65249
  const manifestFiles = inspectedFiles.map((file2) => file2.manifest);
65055
- const diagnostics = collectDiagnostics(inspectedFiles.find((file2) => file2.manifest.path === "SKILL.md")?.bytes, parsedMetadata.metadata.license, new Set(manifestFiles.map((file2) => file2.path)));
65250
+ const diagnostics = [
65251
+ ...parsedMetadata.conformance,
65252
+ ...collectDiagnostics(inspectedFiles.find((file2) => file2.manifest.path === "SKILL.md")?.bytes, parsedMetadata.metadata.license, new Set(manifestFiles.map((file2) => file2.path)))
65253
+ ];
65056
65254
  return {
65057
65255
  schemaVersion: 1,
65058
65256
  entrypoint: "SKILL.md",
@@ -65294,14 +65492,14 @@ function parseSkillMetadata(bytes, expectedName) {
65294
65492
  if (!isRecord2(parsed)) {
65295
65493
  throw new SkillPackageError("INVALID_FRONTMATTER", "SKILL.md frontmatter must be a YAML mapping", "SKILL.md");
65296
65494
  }
65297
- const result = skillFrontmatterSchema.safeParse(parsed);
65298
- if (!result.success) {
65299
- throw new SkillPackageError("INVALID_FRONTMATTER", `SKILL.md frontmatter is invalid: ${result.error.issues.map(formatFrontmatterIssue).join("; ")}`, "SKILL.md");
65495
+ const admitted = skillFrontmatterAdmissionSchema.safeParse(parsed);
65496
+ if (!admitted.success) {
65497
+ throw new SkillPackageError("INVALID_FRONTMATTER", `SKILL.md frontmatter is invalid: ${admitted.error.issues.map(formatFrontmatterIssue).join("; ")}`, "SKILL.md");
65300
65498
  }
65301
- if (expectedName !== null && result.data.name !== expectedName.normalize("NFC")) {
65302
- throw new SkillPackageError("DIRECTORY_NAME_MISMATCH", `Skill name ${result.data.name} must match its directory ${expectedName}`, "SKILL.md");
65499
+ if (expectedName !== null && admitted.data.name !== expectedName.normalize("NFC")) {
65500
+ throw new SkillPackageError("DIRECTORY_NAME_MISMATCH", `Skill name ${admitted.data.name} must match its directory ${expectedName}`, "SKILL.md");
65303
65501
  }
65304
- return { metadata: result.data };
65502
+ return projectSkillFrontmatter(admitted.data);
65305
65503
  }
65306
65504
  function formatFrontmatterIssue(issue2) {
65307
65505
  const field = issue2.path.map(String).join(".");
@@ -68694,6 +68892,332 @@ function decodeResumeFallbackContext(encoded) {
68694
68892
  // src/sandbox.ts
68695
68893
  var import_node_os12 = require("os");
68696
68894
 
68895
+ // src/execution-activity-tracker.ts
68896
+ var ExecutionActivityTracker = class {
68897
+ constructor(onChange) {
68898
+ this.onChange = onChange;
68899
+ }
68900
+ activities = /* @__PURE__ */ new Map();
68901
+ register(input) {
68902
+ const normalized = normalizeRegistration(input);
68903
+ const existing = this.activities.get(normalized.ownerId);
68904
+ if (existing) {
68905
+ if (!sameRegistration(existing.input, normalized)) {
68906
+ throw new Error(`Execution activity owner ${normalized.ownerId} changed identity`);
68907
+ }
68908
+ existing.refCount += 1;
68909
+ } else {
68910
+ const activeLease = this.firstActivity()?.input.leaseRunId;
68911
+ if (activeLease && activeLease !== normalized.leaseRunId) {
68912
+ throw new Error(
68913
+ `Cannot track execution leases ${activeLease} and ${normalized.leaseRunId} concurrently`
68914
+ );
68915
+ }
68916
+ this.activities.set(normalized.ownerId, { input: normalized, refCount: 1 });
68917
+ }
68918
+ this.emitChange();
68919
+ let released = false;
68920
+ return {
68921
+ release: () => {
68922
+ if (released) return;
68923
+ released = true;
68924
+ const tracked = this.activities.get(normalized.ownerId);
68925
+ if (!tracked) return;
68926
+ tracked.refCount -= 1;
68927
+ if (tracked.refCount <= 0) this.activities.delete(normalized.ownerId);
68928
+ this.emitChange();
68929
+ }
68930
+ };
68931
+ }
68932
+ snapshot() {
68933
+ const first = this.firstActivity();
68934
+ if (!first) return null;
68935
+ const activeRunIds = /* @__PURE__ */ new Set([first.input.leaseRunId]);
68936
+ let trackedChildCount = 0;
68937
+ let trackedWorkCount = 0;
68938
+ let childAlive = false;
68939
+ let phase;
68940
+ for (const { input, refCount } of this.activities.values()) {
68941
+ activeRunIds.add(input.activeRunId ?? input.leaseRunId);
68942
+ trackedWorkCount += refCount;
68943
+ if (input.kind === "child") trackedChildCount += refCount;
68944
+ childAlive ||= input.childAlive === true;
68945
+ phase ??= input.phase;
68946
+ }
68947
+ return {
68948
+ runId: first.input.leaseRunId,
68949
+ conversationId: first.input.conversationId,
68950
+ taskId: first.input.taskId,
68951
+ sandboxId: first.input.sandboxId,
68952
+ activeRunIds: [...activeRunIds].sort(),
68953
+ phase,
68954
+ childAlive,
68955
+ trackedChildCount,
68956
+ trackedWorkCount
68957
+ };
68958
+ }
68959
+ firstActivity() {
68960
+ return this.activities.values().next().value;
68961
+ }
68962
+ emitChange() {
68963
+ this.onChange(this.snapshot());
68964
+ }
68965
+ };
68966
+ function normalizeRegistration(input) {
68967
+ const ownerId = input.ownerId.trim();
68968
+ const leaseRunId = input.leaseRunId.trim();
68969
+ const activeRunId = input.activeRunId?.trim() || void 0;
68970
+ const conversationId = input.conversationId.trim();
68971
+ const taskId = input.taskId.trim();
68972
+ const sandboxId = input.sandboxId.trim();
68973
+ if (!ownerId || !leaseRunId || !conversationId || !taskId || !sandboxId) {
68974
+ throw new Error("Execution activity identity fields must be non-empty");
68975
+ }
68976
+ return {
68977
+ ...input,
68978
+ ownerId,
68979
+ leaseRunId,
68980
+ activeRunId,
68981
+ conversationId,
68982
+ taskId,
68983
+ sandboxId
68984
+ };
68985
+ }
68986
+ function sameRegistration(left, right) {
68987
+ return left.ownerId === right.ownerId && left.leaseRunId === right.leaseRunId && left.activeRunId === right.activeRunId && left.conversationId === right.conversationId && left.taskId === right.taskId && left.sandboxId === right.sandboxId && left.kind === right.kind;
68988
+ }
68989
+
68990
+ // src/execution-lease-client.ts
68991
+ var EXECUTION_LEASE_RENEW_INTERVAL_MS = 3e4;
68992
+ var EXECUTION_LEASE_REQUEST_TIMEOUT_MS = 1e4;
68993
+ var DEFAULT_RETRY_DELAYS_MS = [250, 1e3];
68994
+ var ExecutionLeaseClient = class {
68995
+ constructor(options) {
68996
+ this.options = options;
68997
+ this.apiUrl = options.apiUrl.replace(/\/+$/, "");
68998
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
68999
+ this.renewIntervalMs = options.renewIntervalMs ?? EXECUTION_LEASE_RENEW_INTERVAL_MS;
69000
+ this.requestTimeoutMs = options.requestTimeoutMs ?? EXECUTION_LEASE_REQUEST_TIMEOUT_MS;
69001
+ this.retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
69002
+ this.enabled = options.enabled !== false;
69003
+ }
69004
+ apiUrl;
69005
+ fetchImpl;
69006
+ renewIntervalMs;
69007
+ requestTimeoutMs;
69008
+ retryDelaysMs;
69009
+ activity = null;
69010
+ activityGeneration = 0;
69011
+ interval = null;
69012
+ inFlight = null;
69013
+ trailingRenewal = false;
69014
+ nextSequence = 1;
69015
+ enabled;
69016
+ closed = false;
69017
+ terminalSessionFailure = false;
69018
+ terminalRunIds = /* @__PURE__ */ new Set();
69019
+ setActivity(activity) {
69020
+ if (this.closed || this.terminalSessionFailure) return;
69021
+ const previousRunId = this.activity?.runId ?? null;
69022
+ const nextRunId = activity?.runId ?? null;
69023
+ if (previousRunId !== nextRunId) this.activityGeneration += 1;
69024
+ this.activity = activity;
69025
+ if (!activity) {
69026
+ this.stopInterval();
69027
+ this.trailingRenewal = false;
69028
+ this.options.lifecycle?.debug("sandbox_agent_execution_lease_stopped", {
69029
+ reason: "idle"
69030
+ });
69031
+ return;
69032
+ }
69033
+ if (this.terminalRunIds.has(activity.runId)) {
69034
+ this.stopInterval();
69035
+ return;
69036
+ }
69037
+ if (!this.enabled) return;
69038
+ this.startInterval();
69039
+ if (previousRunId !== activity.runId || !this.inFlight) this.requestRenewal();
69040
+ else this.trailingRenewal = true;
69041
+ }
69042
+ /** Enable renewals after the connected WS server proves route support. */
69043
+ enable() {
69044
+ if (this.enabled || this.closed || this.terminalSessionFailure) return;
69045
+ this.enabled = true;
69046
+ this.options.lifecycle?.info("sandbox_agent_execution_lease_enabled");
69047
+ if (!this.activity || this.terminalRunIds.has(this.activity.runId)) return;
69048
+ this.startInterval();
69049
+ this.requestRenewal();
69050
+ }
69051
+ close() {
69052
+ this.closed = true;
69053
+ this.activityGeneration += 1;
69054
+ this.activity = null;
69055
+ this.trailingRenewal = false;
69056
+ this.stopInterval();
69057
+ }
69058
+ startInterval() {
69059
+ if (this.interval) return;
69060
+ this.interval = setInterval(() => this.requestRenewal(), this.renewIntervalMs);
69061
+ this.interval.unref?.();
69062
+ }
69063
+ stopInterval() {
69064
+ if (!this.interval) return;
69065
+ clearInterval(this.interval);
69066
+ this.interval = null;
69067
+ }
69068
+ loseAuthority(activity, seq, status, code) {
69069
+ this.terminalSessionFailure = true;
69070
+ this.stopInterval();
69071
+ this.options.lifecycle?.error("sandbox_agent_execution_lease_authority_lost", {
69072
+ runId: activity.runId,
69073
+ seq,
69074
+ status,
69075
+ code
69076
+ });
69077
+ this.options.onAuthorityLost?.({ runId: activity.runId, code, status });
69078
+ }
69079
+ requestRenewal() {
69080
+ if (this.closed || !this.enabled || this.terminalSessionFailure || !this.activity || this.terminalRunIds.has(this.activity.runId)) {
69081
+ return;
69082
+ }
69083
+ if (this.inFlight) {
69084
+ this.trailingRenewal = true;
69085
+ return;
69086
+ }
69087
+ const activity = this.activity;
69088
+ const activityGeneration = this.activityGeneration;
69089
+ const seq = this.nextSequence;
69090
+ this.nextSequence += 1;
69091
+ this.inFlight = this.renewWithRetry(activity, activityGeneration, seq).finally(() => {
69092
+ this.inFlight = null;
69093
+ if (!this.trailingRenewal) return;
69094
+ this.trailingRenewal = false;
69095
+ if (this.activity) this.requestRenewal();
69096
+ });
69097
+ }
69098
+ isCurrentRun(activity, generation) {
69099
+ return !this.closed && this.activityGeneration === generation && this.activity?.runId === activity.runId;
69100
+ }
69101
+ async renewWithRetry(activity, activityGeneration, seq) {
69102
+ const body = {
69103
+ conversationId: activity.conversationId,
69104
+ taskId: activity.taskId,
69105
+ sandboxId: activity.sandboxId,
69106
+ agentSessionId: this.options.agentSessionId,
69107
+ seq,
69108
+ activeRunIds: activity.activeRunIds,
69109
+ agentVersion: this.options.agentVersion,
69110
+ phase: activity.phase,
69111
+ childAlive: activity.childAlive,
69112
+ trackedChildCount: activity.trackedChildCount
69113
+ };
69114
+ const url3 = `${this.apiUrl}/public/agent-runs/${encodeURIComponent(activity.runId)}/execution-lease`;
69115
+ for (let attempt = 0; attempt <= this.retryDelaysMs.length; attempt += 1) {
69116
+ if (!this.isCurrentRun(activity, activityGeneration)) return;
69117
+ try {
69118
+ const response = await this.fetchImpl(url3, {
69119
+ method: "POST",
69120
+ headers: {
69121
+ authorization: `Bearer ${this.options.sessionToken}`,
69122
+ "content-type": "application/json"
69123
+ },
69124
+ body: JSON.stringify(body),
69125
+ signal: AbortSignal.timeout(this.requestTimeoutMs)
69126
+ });
69127
+ if (response.ok) {
69128
+ this.options.lifecycle?.debug("sandbox_agent_execution_lease_renewed", {
69129
+ runId: activity.runId,
69130
+ seq,
69131
+ attempt: attempt + 1
69132
+ });
69133
+ return;
69134
+ }
69135
+ const errorBody = await readErrorBody(response);
69136
+ const code = errorBody.code;
69137
+ const sessionAuthorityLost = response.status === 409 && code === "EXECUTION_LEASE_FENCED" || response.status === 401 && code === "EXECUTION_LEASE_INVALID_TOKEN";
69138
+ if (sessionAuthorityLost && code) {
69139
+ this.loseAuthority(activity, seq, response.status, code);
69140
+ return;
69141
+ }
69142
+ const runAuthorityLost = response.status === 409 && code === "EXECUTION_LEASE_INACTIVE_RUN" || response.status === 403 && code === "EXECUTION_LEASE_BINDING_MISMATCH" || response.status === 404 && code === "EXECUTION_LEASE_RUN_NOT_FOUND";
69143
+ if (runAuthorityLost && code) {
69144
+ if (!this.isCurrentRun(activity, activityGeneration)) {
69145
+ this.options.lifecycle?.debug("sandbox_agent_execution_lease_stale_response_ignored", {
69146
+ runId: activity.runId,
69147
+ seq,
69148
+ status: response.status,
69149
+ code
69150
+ });
69151
+ return;
69152
+ }
69153
+ this.loseAuthority(activity, seq, response.status, code);
69154
+ return;
69155
+ }
69156
+ if (response.status === 409 && code === "EXECUTION_LEASE_PROVIDER_UNRESOLVED") {
69157
+ this.terminalRunIds.add(activity.runId);
69158
+ if (this.activity?.runId === activity.runId) this.stopInterval();
69159
+ this.options.lifecycle?.warn("sandbox_agent_execution_lease_run_rejected", {
69160
+ runId: activity.runId,
69161
+ seq,
69162
+ code
69163
+ });
69164
+ return;
69165
+ }
69166
+ const takeoverPending = response.status === 409 && code === "EXECUTION_LEASE_TAKEOVER_PENDING";
69167
+ const mixedVersionUnavailable = (response.status === 401 || response.status === 403 || response.status === 404) && !code?.startsWith("EXECUTION_LEASE_");
69168
+ const retryable = takeoverPending || mixedVersionUnavailable || isRetryableStatus(response.status);
69169
+ if (!retryable || attempt === this.retryDelaysMs.length) {
69170
+ this.options.lifecycle?.warn("sandbox_agent_execution_lease_renew_failed", {
69171
+ runId: activity.runId,
69172
+ seq,
69173
+ status: response.status,
69174
+ code,
69175
+ attempt: attempt + 1
69176
+ });
69177
+ return;
69178
+ }
69179
+ if (takeoverPending) {
69180
+ this.options.lifecycle?.debug("sandbox_agent_execution_lease_takeover_pending", {
69181
+ runId: activity.runId,
69182
+ seq,
69183
+ attempt: attempt + 1
69184
+ });
69185
+ }
69186
+ } catch (error2) {
69187
+ if (attempt === this.retryDelaysMs.length) {
69188
+ this.options.lifecycle?.warn("sandbox_agent_execution_lease_renew_failed", {
69189
+ runId: activity.runId,
69190
+ seq,
69191
+ attempt: attempt + 1,
69192
+ error: error2 instanceof Error ? error2 : new Error(String(error2))
69193
+ });
69194
+ return;
69195
+ }
69196
+ }
69197
+ await delay2(this.retryDelaysMs[attempt] ?? 0);
69198
+ }
69199
+ }
69200
+ };
69201
+ function isRetryableStatus(status) {
69202
+ return status === 408 || status === 429 || status >= 500;
69203
+ }
69204
+ async function readErrorBody(response) {
69205
+ try {
69206
+ const body = await response.json();
69207
+ if (!body || typeof body !== "object") return {};
69208
+ const record2 = body;
69209
+ return {
69210
+ code: typeof record2.code === "string" ? record2.code : void 0,
69211
+ message: typeof record2.message === "string" ? record2.message : void 0
69212
+ };
69213
+ } catch {
69214
+ return {};
69215
+ }
69216
+ }
69217
+ function delay2(ms) {
69218
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
69219
+ }
69220
+
68697
69221
  // src/web-presenter.ts
68698
69222
  var import_node_crypto15 = require("crypto");
68699
69223
  var WebPresenter = class {
@@ -69086,6 +69610,21 @@ var SandboxEventDispatcher = class {
69086
69610
 
69087
69611
  // src/ws-client.ts
69088
69612
  var ACCEPTED_MESSAGE_ID_CACHE_SIZE = 200;
69613
+ var SUPERVISED_RECONNECT_BASE_DELAY_MS = 1e3;
69614
+ var SUPERVISED_RECONNECT_MAX_DELAY_MS = 5e3;
69615
+ var SUPERVISED_RECONNECT_MAX_JITTER_MS = 500;
69616
+ function parseConnectionDirective(data) {
69617
+ if (typeof data !== "object" || data === null || typeof data.code !== "string" || typeof data.retryable !== "boolean") {
69618
+ return null;
69619
+ }
69620
+ const candidate = data;
69621
+ return {
69622
+ code: candidate.code,
69623
+ retryable: candidate.retryable,
69624
+ reason: typeof candidate.reason === "string" ? candidate.reason : void 0,
69625
+ retryAfterMs: typeof candidate.retryAfterMs === "number" && Number.isFinite(candidate.retryAfterMs) && candidate.retryAfterMs >= 0 ? candidate.retryAfterMs : void 0
69626
+ };
69627
+ }
69089
69628
  var WSClient = class {
69090
69629
  constructor(wsUrl, sessionId, taskId, token, callbacks, agentVersion, lifecycle) {
69091
69630
  this.lifecycle = lifecycle;
@@ -69094,7 +69633,7 @@ var WSClient = class {
69094
69633
  this.getRunState = callbacks.getRunState;
69095
69634
  this.socket = lookup(wsUrl, {
69096
69635
  query: { type: "agent", sessionId, taskId, agentVersion: agentVersion ?? "" },
69097
- auth: { token },
69636
+ auth: { token, agentSessionId: this.agentSessionId },
69098
69637
  transports: ["websocket"],
69099
69638
  reconnection: true,
69100
69639
  reconnectionAttempts: Infinity,
@@ -69126,6 +69665,10 @@ var WSClient = class {
69126
69665
  /** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
69127
69666
  heartbeatSeq = 0;
69128
69667
  heartbeatTimer = null;
69668
+ supervisedReconnectTimer = null;
69669
+ supervisedReconnectAttempt = 0;
69670
+ connectionDirective = null;
69671
+ intentionalClose = false;
69129
69672
  agentVersion;
69130
69673
  getRunState;
69131
69674
  /**
@@ -69232,16 +69775,82 @@ var WSClient = class {
69232
69775
  get connected() {
69233
69776
  return this.socket.connected;
69234
69777
  }
69778
+ getAgentSessionId() {
69779
+ return this.agentSessionId;
69780
+ }
69235
69781
  setLifecycleContext(context) {
69236
69782
  this.lifecycle?.setContext(context);
69237
69783
  }
69238
69784
  close() {
69239
69785
  this.lifecycle?.info("sandbox_agent_ws_close_requested");
69786
+ this.intentionalClose = true;
69787
+ this.clearSupervisedReconnect();
69240
69788
  this.stopHeartbeat();
69241
69789
  this.socket.disconnect();
69242
69790
  }
69791
+ clearSupervisedReconnect() {
69792
+ if (this.supervisedReconnectTimer) {
69793
+ clearTimeout(this.supervisedReconnectTimer);
69794
+ this.supervisedReconnectTimer = null;
69795
+ }
69796
+ }
69797
+ /**
69798
+ * Socket.IO reconnects transport failures through its Manager. It does not
69799
+ * reconnect an `io server disconnect`, where `socket.active` is false. Only
69800
+ * supervise that terminal transport state, and only after the server has
69801
+ * explicitly classified the disconnect as retryable (including the legacy
69802
+ * `server_shutdown` advisory emitted by older WS servers).
69803
+ */
69804
+ scheduleSupervisedReconnect(reason) {
69805
+ if (this.intentionalClose || this.socket.connected || this.socket.active || this.supervisedReconnectTimer || this.connectionDirective?.retryable !== true) {
69806
+ return;
69807
+ }
69808
+ const attempt = this.supervisedReconnectAttempt + 1;
69809
+ this.supervisedReconnectAttempt = attempt;
69810
+ const exponentialDelay = Math.min(
69811
+ SUPERVISED_RECONNECT_BASE_DELAY_MS * 2 ** (attempt - 1),
69812
+ SUPERVISED_RECONNECT_MAX_DELAY_MS
69813
+ );
69814
+ const requestedDelay = Math.max(exponentialDelay, this.connectionDirective.retryAfterMs ?? 0);
69815
+ const jitterRange = Math.min(SUPERVISED_RECONNECT_MAX_JITTER_MS, requestedDelay / 2);
69816
+ const delayMs = requestedDelay + Math.floor(Math.random() * jitterRange);
69817
+ this.lifecycle?.warn("sandbox_agent_ws_supervised_reconnect_scheduled", {
69818
+ reason,
69819
+ directiveCode: this.connectionDirective.code,
69820
+ attempt,
69821
+ delayMs
69822
+ });
69823
+ this.supervisedReconnectTimer = setTimeout(() => {
69824
+ this.supervisedReconnectTimer = null;
69825
+ if (this.intentionalClose || this.socket.connected || this.socket.active) return;
69826
+ this.lifecycle?.info("sandbox_agent_ws_supervised_reconnect_attempt", {
69827
+ directiveCode: this.connectionDirective?.code,
69828
+ attempt
69829
+ });
69830
+ this.connectionDirective = null;
69831
+ this.socket.connect();
69832
+ }, delayMs);
69833
+ this.supervisedReconnectTimer.unref?.();
69834
+ }
69243
69835
  setupListeners(callbacks) {
69836
+ this.socket.io.on("reconnect_attempt", (attempt) => {
69837
+ this.lifecycle?.info("sandbox_agent_ws_manager_reconnect_attempt", { attempt });
69838
+ });
69839
+ this.socket.io.on("reconnect", (attempt) => {
69840
+ this.lifecycle?.info("sandbox_agent_ws_manager_reconnected", { attempt });
69841
+ });
69842
+ this.socket.io.on("reconnect_error", (error2) => {
69843
+ this.lifecycle?.warn("sandbox_agent_ws_manager_reconnect_error", {
69844
+ error: error2 instanceof Error ? error2 : new Error(String(error2))
69845
+ });
69846
+ });
69847
+ this.socket.io.on("reconnect_failed", () => {
69848
+ this.lifecycle?.error("sandbox_agent_ws_manager_reconnect_failed");
69849
+ });
69244
69850
  this.socket.on("connect", () => {
69851
+ this.clearSupervisedReconnect();
69852
+ this.supervisedReconnectAttempt = 0;
69853
+ this.connectionDirective = null;
69245
69854
  this.lifecycle?.info("sandbox_agent_ws_socket_connected", { socketId: this.socket.id });
69246
69855
  this.startHeartbeat();
69247
69856
  this.eventDispatcher.flush();
@@ -69252,6 +69861,36 @@ var WSClient = class {
69252
69861
  this.stopHeartbeat();
69253
69862
  this.eventDispatcher.disconnect();
69254
69863
  callbacks.onDisconnect?.(reason);
69864
+ this.scheduleSupervisedReconnect(reason);
69865
+ });
69866
+ this.socket.on("server_shutdown", (data) => {
69867
+ this.connectionDirective = {
69868
+ code: "server_shutdown",
69869
+ reason: data?.reason,
69870
+ retryable: true,
69871
+ retryAfterMs: data?.retryAfterMs ?? 1e3
69872
+ };
69873
+ });
69874
+ this.socket.on("agent.capabilities", (data) => {
69875
+ if (typeof data !== "object" || data === null || typeof data.executionLeaseV1 !== "boolean") {
69876
+ return;
69877
+ }
69878
+ const capabilities = {
69879
+ executionLeaseV1: data.executionLeaseV1
69880
+ };
69881
+ this.lifecycle?.info("sandbox_agent_ws_capabilities_received", {
69882
+ executionLeaseV1: capabilities.executionLeaseV1
69883
+ });
69884
+ callbacks.onCapabilities?.(capabilities);
69885
+ });
69886
+ this.socket.on("connection_directive", (data) => {
69887
+ const directive = parseConnectionDirective(data);
69888
+ if (!directive) return;
69889
+ this.connectionDirective = directive;
69890
+ if (!directive.retryable) {
69891
+ this.clearSupervisedReconnect();
69892
+ callbacks.onTerminalDirective?.({ code: directive.code, reason: directive.reason });
69893
+ }
69255
69894
  });
69256
69895
  this.socket.on("agent.probe", (_data, ack) => {
69257
69896
  ack?.({
@@ -69261,9 +69900,24 @@ var WSClient = class {
69261
69900
  });
69262
69901
  });
69263
69902
  this.socket.on("connect_error", (error2) => {
69903
+ const directive = parseConnectionDirective(
69904
+ error2?.data
69905
+ );
69906
+ if (directive) {
69907
+ this.connectionDirective = directive;
69908
+ if (!directive.retryable) {
69909
+ this.clearSupervisedReconnect();
69910
+ callbacks.onTerminalDirective?.({ code: directive.code, reason: directive.reason });
69911
+ }
69912
+ } else {
69913
+ this.connectionDirective = null;
69914
+ }
69264
69915
  this.lifecycle?.warn("sandbox_agent_ws_socket_connect_error", {
69265
- error: error2 instanceof Error ? error2 : new Error(String(error2))
69916
+ error: error2 instanceof Error ? error2 : new Error(String(error2)),
69917
+ directiveCode: directive?.code,
69918
+ retryable: directive?.retryable
69266
69919
  });
69920
+ this.scheduleSupervisedReconnect("connect_error");
69267
69921
  });
69268
69922
  this.socket.on(
69269
69923
  "user_message",
@@ -69367,10 +70021,14 @@ async function runSandbox(config2) {
69367
70021
  let currentImages = [];
69368
70022
  let currentFiles = [];
69369
70023
  let currentParsedAttachments = [];
69370
- const stopped = false;
70024
+ let stopped = false;
69371
70025
  let pendingMessageResolve = null;
69372
70026
  const queuedMessages = [];
69373
70027
  let currentAgent = null;
70028
+ let executionLeaseClient = null;
70029
+ let executionLeaseV1Supported = false;
70030
+ let activeRootExecutionActivity = null;
70031
+ let reportActiveRun = false;
69374
70032
  let currentTaskMeta;
69375
70033
  let currentPrMeta;
69376
70034
  let currentWorkflowTools;
@@ -69398,6 +70056,32 @@ async function runSandbox(config2) {
69398
70056
  lifecycle.info("sandbox_agent_ws_connect_attempt", {
69399
70057
  wsUrl: config2.wsUrl
69400
70058
  });
70059
+ const stopActiveAgent = () => {
70060
+ reportActiveRun = false;
70061
+ activeRootExecutionActivity?.release();
70062
+ activeRootExecutionActivity = null;
70063
+ const agent = currentAgent;
70064
+ if (!agent) return;
70065
+ const killed = agent.kill();
70066
+ if (!killed) {
70067
+ agent.abort();
70068
+ return;
70069
+ }
70070
+ const abortDeadline = setTimeout(() => {
70071
+ if (currentAgent === agent) agent.abort();
70072
+ }, 5e3);
70073
+ abortDeadline.unref?.();
70074
+ };
70075
+ const terminateAgentProcess = (reason) => {
70076
+ if (stopped) return;
70077
+ lifecycle.warn("sandbox_agent_process_fenced", reason);
70078
+ stopped = true;
70079
+ executionLeaseClient?.close();
70080
+ stopActiveAgent();
70081
+ const resolvePendingMessage = pendingMessageResolve;
70082
+ pendingMessageResolve = null;
70083
+ resolvePendingMessage?.(null);
70084
+ };
69401
70085
  const wsClient = new WSClient(
69402
70086
  config2.wsUrl,
69403
70087
  config2.sessionId,
@@ -69440,10 +70124,7 @@ async function runSandbox(config2) {
69440
70124
  },
69441
70125
  onStop: () => {
69442
70126
  lifecycle.info("sandbox_agent_stop_received");
69443
- if (currentAgent) {
69444
- const killed = currentAgent.kill();
69445
- if (!killed) currentAgent.abort();
69446
- }
70127
+ stopActiveAgent();
69447
70128
  },
69448
70129
  onToolResponse: (toolId, response) => {
69449
70130
  if (currentAgent) {
@@ -69457,12 +70138,40 @@ async function runSandbox(config2) {
69457
70138
  },
69458
70139
  onConnect: () => lifecycle.info("sandbox_agent_ws_connected"),
69459
70140
  onDisconnect: (reason) => lifecycle.info("sandbox_agent_ws_disconnected", { reason }),
70141
+ onCapabilities: (capabilities) => {
70142
+ if (!capabilities.executionLeaseV1) return;
70143
+ executionLeaseV1Supported = true;
70144
+ executionLeaseClient?.enable();
70145
+ },
70146
+ onTerminalDirective: terminateAgentProcess,
69460
70147
  // Liveness heartbeat run state: a run is active while CoreAgent is executing.
69461
- getRunState: () => currentAgent ? { idle: false, activeRunIds: currentRunId ? [currentRunId] : [] } : { idle: true, activeRunIds: [] }
70148
+ getRunState: () => {
70149
+ const activeRunId = currentRunId ?? config2.runId;
70150
+ return currentAgent && reportActiveRun ? { idle: false, activeRunIds: activeRunId ? [activeRunId] : [] } : { idle: true, activeRunIds: [] };
70151
+ }
69462
70152
  },
69463
70153
  AGENT_VERSION,
69464
70154
  lifecycle
69465
70155
  );
70156
+ const agentSessionId = wsClient.getAgentSessionId();
70157
+ executionLeaseClient = config2.apiUrl && config2.sandboxId && agentSessionId ? new ExecutionLeaseClient({
70158
+ apiUrl: config2.apiUrl,
70159
+ sessionToken: config2.sessionToken,
70160
+ agentSessionId,
70161
+ agentVersion: AGENT_VERSION,
70162
+ lifecycle,
70163
+ enabled: false,
70164
+ onAuthorityLost: ({ code }) => terminateAgentProcess({ code })
70165
+ }) : null;
70166
+ if (executionLeaseV1Supported) executionLeaseClient?.enable();
70167
+ const executionActivityTracker = executionLeaseClient ? new ExecutionActivityTracker((activity) => executionLeaseClient.setActivity(activity)) : null;
70168
+ if (!executionLeaseClient && (currentRunId ?? config2.runId)) {
70169
+ lifecycle.warn("sandbox_agent_execution_lease_unavailable", {
70170
+ hasApiUrl: Boolean(config2.apiUrl),
70171
+ hasSandboxId: Boolean(config2.sandboxId),
70172
+ hasAgentSessionId: Boolean(agentSessionId)
70173
+ });
70174
+ }
69466
70175
  try {
69467
70176
  await wsClient.waitForConnection();
69468
70177
  } catch (error2) {
@@ -69490,6 +70199,18 @@ async function runSandbox(config2) {
69490
70199
  backendKind: activeBackendKind
69491
70200
  });
69492
70201
  currentAgent = agent;
70202
+ const activeRunId = currentRunId ?? config2.runId;
70203
+ const rootExecutionActivity = executionActivityTracker && activeRunId && currentTaskId ? executionActivityTracker.register({
70204
+ ownerId: `root:${activeRunId}`,
70205
+ leaseRunId: activeRunId,
70206
+ conversationId: currentConversationId,
70207
+ taskId: currentTaskId,
70208
+ sandboxId: config2.sandboxId,
70209
+ kind: "root",
70210
+ phase: "agent_run"
70211
+ }) : null;
70212
+ activeRootExecutionActivity = rootExecutionActivity;
70213
+ reportActiveRun = true;
69493
70214
  wsClient.setCurrentRunId(currentRunId ?? config2.runId);
69494
70215
  presenter.setRunContext?.(currentRunId ?? config2.runId, activeBackendKind);
69495
70216
  const correlation = correlationLogFields2({
@@ -69642,6 +70363,12 @@ async function runSandbox(config2) {
69642
70363
  presenter.onError(errorMessage, false);
69643
70364
  }
69644
70365
  presenter.onComplete(result);
70366
+ } finally {
70367
+ reportActiveRun = false;
70368
+ rootExecutionActivity?.release();
70369
+ if (activeRootExecutionActivity === rootExecutionActivity) {
70370
+ activeRootExecutionActivity = null;
70371
+ }
69645
70372
  }
69646
70373
  currentAgent = null;
69647
70374
  resumeFallbackContext = void 0;
@@ -69733,6 +70460,7 @@ async function runSandbox(config2) {
69733
70460
  }
69734
70461
  presenter.setSuppressSessionLifecycle(false);
69735
70462
  lifecycle.info("sandbox_agent_process_exit");
70463
+ executionLeaseClient?.close();
69736
70464
  wsClient.close();
69737
70465
  }
69738
70466
 
@@ -70098,6 +70826,7 @@ function logEffectiveGitIdentity(lifecycle, projectPath) {
70098
70826
  }
70099
70827
  async function runSessionFromEnv() {
70100
70828
  const wsUrl = process.env.ALAN_WS_URL;
70829
+ const apiUrl = process.env.ALAN_API_URL || void 0;
70101
70830
  const sessionId = process.env.ALAN_SESSION_ID;
70102
70831
  const taskId = process.env.ALAN_TASK_ID;
70103
70832
  const sessionToken = process.env.ALAN_SESSION_TOKEN;
@@ -70143,6 +70872,7 @@ async function runSessionFromEnv() {
70143
70872
  logEffectiveGitIdentity(lifecycle, projectPath);
70144
70873
  await runSandbox({
70145
70874
  wsUrl,
70875
+ apiUrl,
70146
70876
  sessionId,
70147
70877
  taskId,
70148
70878
  sessionToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiden-ade/sandbox-agent",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
4
4
  "type": "module",
5
5
  "description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "lint": "biome check .",
36
36
  "lint:fix": "biome check --write .",
37
37
  "format": "biome format --write .",
38
- "test": "vitest run src/__tests__/daemon-socket.integration.test.ts && vitest run --exclude src/__tests__/daemon-socket.integration.test.ts",
38
+ "test": "vitest run",
39
39
  "test:coverage": "vitest run --coverage",
40
40
  "test:watch": "vitest",
41
41
  "type-check": "tsc --noEmit",