@vtxmacro/cli 2026.8.52 → 2026.8.53

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 (3) hide show
  1. package/README.md +42 -18
  2. package/bin/vtx.js +1080 -113
  3. package/package.json +1 -1
package/bin/vtx.js CHANGED
@@ -47,7 +47,7 @@ var init_agent_cli_release = __esm({
47
47
  "agent-cli-release.json"() {
48
48
  agent_cli_release_default = {
49
49
  package_name: "@vtxmacro/cli",
50
- package_version: "2026.8.52",
50
+ package_version: "2026.8.53",
51
51
  codex_package_name: "@openai/codex",
52
52
  codex_version: "0.147.0",
53
53
  copilot_sdk_package_name: "@github/copilot-sdk",
@@ -19695,32 +19695,60 @@ var init_agent_client = __esm({
19695
19695
  });
19696
19696
 
19697
19697
  // lib/inference-host/agent-state.ts
19698
- async function readCodexAgentRuntimeState(statePath) {
19698
+ async function readInferenceAgentRuntimeState(statePath) {
19699
19699
  const raw = await readInferencePrivateFile(
19700
- codexAgentRuntimeStatePath(statePath),
19701
- "Codex Agent runtime recovery state"
19700
+ inferenceAgentRuntimeStatePath(statePath),
19701
+ "Inference Agent runtime recovery state"
19702
19702
  );
19703
19703
  if (raw === null) return null;
19704
19704
  try {
19705
- return assertCodexAgentRuntimeState(JSON.parse(raw));
19705
+ return assertInferenceAgentRuntimeState(JSON.parse(raw));
19706
19706
  } catch (error48) {
19707
19707
  if (error48 instanceof SyntaxError) {
19708
- throw new Error("Codex Agent runtime recovery state is not valid JSON.");
19708
+ throw new Error("Inference Agent runtime recovery state is not valid JSON.");
19709
19709
  }
19710
19710
  throw error48;
19711
19711
  }
19712
19712
  }
19713
- async function writeCodexAgentRuntimeState(statePath, value) {
19713
+ async function writeInferenceAgentRuntimeState(statePath, value) {
19714
19714
  await writeAtomicInferencePrivateFile(
19715
- codexAgentRuntimeStatePath(statePath),
19716
- `${JSON.stringify(assertCodexAgentRuntimeState(value), null, 2)}
19715
+ inferenceAgentRuntimeStatePath(statePath),
19716
+ `${JSON.stringify(assertInferenceAgentRuntimeState(value), null, 2)}
19717
19717
  `
19718
19718
  );
19719
19719
  }
19720
- async function clearCodexAgentRuntimeState(statePath) {
19720
+ async function clearInferenceAgentRuntimeState(statePath) {
19721
19721
  await clearInferencePrivateFile(
19722
- codexAgentRuntimeStatePath(statePath),
19723
- "Codex Agent runtime recovery state"
19722
+ inferenceAgentRuntimeStatePath(statePath),
19723
+ "Inference Agent runtime recovery state"
19724
+ );
19725
+ }
19726
+ async function readInferenceForegroundAgentControlState(statePath) {
19727
+ const raw = await readInferencePrivateFile(
19728
+ foregroundAgentControlStatePath(statePath),
19729
+ "Foreground Agent control recovery state"
19730
+ );
19731
+ if (raw === null) return null;
19732
+ try {
19733
+ return assertForegroundAgentControlState(JSON.parse(raw));
19734
+ } catch (error48) {
19735
+ if (error48 instanceof SyntaxError) {
19736
+ throw new Error("Foreground Agent control recovery state is not valid JSON.");
19737
+ }
19738
+ throw error48;
19739
+ }
19740
+ }
19741
+ async function writeInferenceForegroundAgentControlState(statePath, value) {
19742
+ await writeAtomicInferencePrivateFile(
19743
+ foregroundAgentControlStatePath(statePath),
19744
+ `${JSON.stringify(assertForegroundAgentControlState(value), null, 2)}
19745
+ `
19746
+ );
19747
+ }
19748
+ async function clearInferenceForegroundAgentControlState(statePath) {
19749
+ await clearInferencePrivateFile(
19750
+ foregroundAgentControlStatePath(statePath),
19751
+ "Foreground Agent control recovery state"
19724
19752
  );
19725
19753
  }
19726
19754
  async function readInferenceAgentAttemptState(statePath) {
@@ -19779,7 +19807,7 @@ async function clearInferenceAgentNextState(statePath) {
19779
19807
  "Agent-driven inference next recovery state"
19780
19808
  );
19781
19809
  }
19782
- var inferenceAgentAttemptStatePath, inferenceAgentNextStatePath, codexAgentRuntimeStatePath, isIsoTimestamp, assertPlainObject, hasExactKeys, assertCodexAgentRuntimeState, assertNextState, assertAttemptState;
19810
+ var inferenceAgentAttemptStatePath, inferenceAgentNextStatePath, inferenceAgentRuntimeStatePath, isIsoTimestamp, assertPlainObject, hasExactKeys, assertInferenceAgentRuntimeState, readCodexAgentRuntimeState, clearCodexAgentRuntimeState, foregroundAgentControlStatePath, assertRuntimeAssignment, assertForegroundAgentControlState, assertNextState, assertAttemptState;
19783
19811
  var init_agent_state = __esm({
19784
19812
  "lib/inference-host/agent-state.ts"() {
19785
19813
  "use strict";
@@ -19787,18 +19815,20 @@ var init_agent_state = __esm({
19787
19815
  init_external_inference_contract();
19788
19816
  inferenceAgentAttemptStatePath = (statePath) => `${statePath}.agent-attempt.json`;
19789
19817
  inferenceAgentNextStatePath = (statePath) => `${statePath}.agent-next.json`;
19790
- codexAgentRuntimeStatePath = (statePath) => `${statePath}.codex-agent-runtime.json`;
19818
+ inferenceAgentRuntimeStatePath = (statePath) => `${statePath}.codex-agent-runtime.json`;
19791
19819
  isIsoTimestamp = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
19792
19820
  assertPlainObject = (value, message) => {
19793
19821
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(message);
19794
19822
  return value;
19795
19823
  };
19796
19824
  hasExactKeys = (record2, keys) => Object.keys(record2).sort().join("\0") === [...keys].sort().join("\0");
19797
- assertCodexAgentRuntimeState = (value) => {
19798
- const message = "Codex Agent runtime recovery state is invalid.";
19825
+ assertInferenceAgentRuntimeState = (value) => {
19826
+ const message = "Inference Agent runtime recovery state is invalid.";
19799
19827
  const state = assertPlainObject(value, message);
19828
+ const generic = state.schema_version === "vtx_inference_agent_runtime_v3";
19800
19829
  if (!hasExactKeys(state, [
19801
19830
  "schema_version",
19831
+ ...generic ? ["adapter_id"] : [],
19802
19832
  "host_id",
19803
19833
  "assignment",
19804
19834
  "thread",
@@ -19807,9 +19837,13 @@ var init_agent_state = __esm({
19807
19837
  "updated_at"
19808
19838
  ])) throw new Error(message);
19809
19839
  const assignment = assertPlainObject(state.assignment, message);
19810
- if (!["vtx_codex_agent_runtime_v1", "vtx_codex_agent_runtime_v2"].includes(
19840
+ if (![
19841
+ "vtx_codex_agent_runtime_v1",
19842
+ "vtx_codex_agent_runtime_v2",
19843
+ "vtx_inference_agent_runtime_v3"
19844
+ ].includes(
19811
19845
  String(state.schema_version)
19812
- ) || typeof state.host_id !== "string" || !state.host_id || !isIsoTimestamp(state.next_wake_at) || !isIsoTimestamp(state.updated_at) || !hasExactKeys(
19846
+ ) || generic && (typeof state.adapter_id !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}$/u.test(state.adapter_id)) || typeof state.host_id !== "string" || !state.host_id || !isIsoTimestamp(state.next_wake_at) || !isIsoTimestamp(state.updated_at) || !hasExactKeys(
19813
19847
  assignment,
19814
19848
  [
19815
19849
  "assignment_id",
@@ -19823,10 +19857,10 @@ var init_agent_state = __esm({
19823
19857
  "minimum_wake_seconds",
19824
19858
  "maximum_wake_seconds",
19825
19859
  "lease_expires_at",
19826
- ...state.schema_version === "vtx_codex_agent_runtime_v2" ? ["data_contract"] : []
19860
+ ...state.schema_version !== "vtx_codex_agent_runtime_v1" ? ["data_contract"] : []
19827
19861
  ]
19828
19862
  ) || typeof assignment.assignment_id !== "string" || !assignment.assignment_id || !Number.isSafeInteger(assignment.assignment_generation) || Number(assignment.assignment_generation) < 1 || typeof assignment.model_id !== "string" || !assignment.model_id || typeof assignment.reasoning_effort !== "string" || !assignment.reasoning_effort || !["trader", "assistant"].includes(String(assignment.bot_mode)) || !["server", "client"].includes(String(assignment.execution_mode)) || !Array.isArray(assignment.allowed_symbols) || assignment.allowed_symbols.some((symbol2) => typeof symbol2 !== "string" || !symbol2) || !assignment.output_schema || typeof assignment.output_schema !== "object" || Array.isArray(assignment.output_schema) || !Number.isSafeInteger(assignment.minimum_wake_seconds) || Number(assignment.minimum_wake_seconds) < 1 || !Number.isSafeInteger(assignment.maximum_wake_seconds) || Number(assignment.maximum_wake_seconds) < Number(assignment.minimum_wake_seconds) || !isIsoTimestamp(assignment.lease_expires_at)) throw new Error(message);
19829
- if (state.schema_version === "vtx_codex_agent_runtime_v2") {
19863
+ if (state.schema_version !== "vtx_codex_agent_runtime_v1") {
19830
19864
  if (!Array.isArray(assignment.data_contract) || assignment.data_contract.length === 0) {
19831
19865
  throw new Error(message);
19832
19866
  }
@@ -19865,6 +19899,76 @@ var init_agent_state = __esm({
19865
19899
  }
19866
19900
  return state;
19867
19901
  };
19902
+ readCodexAgentRuntimeState = readInferenceAgentRuntimeState;
19903
+ clearCodexAgentRuntimeState = clearInferenceAgentRuntimeState;
19904
+ foregroundAgentControlStatePath = (statePath) => `${statePath}.foreground-agent-control.json`;
19905
+ assertRuntimeAssignment = (value, message) => {
19906
+ const assignment = assertPlainObject(value, message);
19907
+ if (!hasExactKeys(assignment, [
19908
+ "assignment_id",
19909
+ "assignment_generation",
19910
+ "model_id",
19911
+ "reasoning_effort",
19912
+ "bot_mode",
19913
+ "execution_mode",
19914
+ "allowed_symbols",
19915
+ "output_schema",
19916
+ "data_contract",
19917
+ "minimum_wake_seconds",
19918
+ "maximum_wake_seconds",
19919
+ "lease_expires_at"
19920
+ ]) || typeof assignment.assignment_id !== "string" || !assignment.assignment_id || !Number.isSafeInteger(assignment.assignment_generation) || Number(assignment.assignment_generation) < 1 || typeof assignment.model_id !== "string" || !assignment.model_id || typeof assignment.reasoning_effort !== "string" || !assignment.reasoning_effort || !["trader", "assistant"].includes(String(assignment.bot_mode)) || !["server", "client"].includes(String(assignment.execution_mode)) || !Array.isArray(assignment.allowed_symbols) || assignment.allowed_symbols.some((symbol2) => typeof symbol2 !== "string" || !symbol2) || !assignment.output_schema || typeof assignment.output_schema !== "object" || Array.isArray(assignment.output_schema) || !Array.isArray(assignment.data_contract) || assignment.data_contract.length === 0 || !Number.isSafeInteger(assignment.minimum_wake_seconds) || Number(assignment.minimum_wake_seconds) < 1 || !Number.isSafeInteger(assignment.maximum_wake_seconds) || Number(assignment.maximum_wake_seconds) < Number(assignment.minimum_wake_seconds) || !isIsoTimestamp(assignment.lease_expires_at)) throw new Error(message);
19921
+ const ids = assignment.data_contract.map((value2) => {
19922
+ const descriptor = assertPlainObject(value2, message);
19923
+ if (!hasExactKeys(descriptor, [
19924
+ "id",
19925
+ "title",
19926
+ "description",
19927
+ "input_schema",
19928
+ "input_schema_sha256"
19929
+ ]) || typeof descriptor.id !== "string" || !descriptor.id || typeof descriptor.title !== "string" || !descriptor.title || typeof descriptor.description !== "string" || !descriptor.description || !descriptor.input_schema || typeof descriptor.input_schema !== "object" || Array.isArray(descriptor.input_schema) || typeof descriptor.input_schema_sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(descriptor.input_schema_sha256)) throw new Error(message);
19930
+ return descriptor.id;
19931
+ });
19932
+ if (new Set(ids).size !== ids.length) throw new Error(message);
19933
+ return assignment;
19934
+ };
19935
+ assertForegroundAgentControlState = (value) => {
19936
+ const message = "Foreground Agent control recovery state is invalid.";
19937
+ const state = assertPlainObject(value, message);
19938
+ if (!hasExactKeys(state, [
19939
+ "schema_version",
19940
+ "host_id",
19941
+ "assignment",
19942
+ "pending_next",
19943
+ "pending_decision",
19944
+ "next_wake_at",
19945
+ "updated_at"
19946
+ ]) || state.schema_version !== "vtx_foreground_agent_control_v1" || typeof state.host_id !== "string" || !state.host_id || state.next_wake_at !== null && !isIsoTimestamp(state.next_wake_at) || !isIsoTimestamp(state.updated_at)) throw new Error(message);
19947
+ if (state.assignment !== null) assertRuntimeAssignment(state.assignment, message);
19948
+ if (state.pending_next !== null) {
19949
+ const pending = assertPlainObject(state.pending_next, message);
19950
+ if (!hasExactKeys(pending, ["operation_id", "requested_at"]) || typeof pending.operation_id !== "string" || !pending.operation_id || !isIsoTimestamp(pending.requested_at)) throw new Error(message);
19951
+ }
19952
+ if (state.pending_decision !== null) {
19953
+ if (state.assignment === null) throw new Error(message);
19954
+ const pending = assertPlainObject(state.pending_decision, message);
19955
+ if (!hasExactKeys(pending, [
19956
+ "request",
19957
+ "first_transmit_at",
19958
+ "last_status_check_at"
19959
+ ])) throw new Error(message);
19960
+ const request = assertPlainObject(pending.request, message);
19961
+ if (!hasExactKeys(request, [
19962
+ "assignment_id",
19963
+ "assignment_generation",
19964
+ "operation_id",
19965
+ "candidate",
19966
+ "observed_at",
19967
+ "provenance"
19968
+ ]) || request.assignment_id !== state.assignment.assignment_id || request.assignment_generation !== state.assignment.assignment_generation || typeof request.operation_id !== "string" || !request.operation_id || !request.candidate || typeof request.candidate !== "object" || Array.isArray(request.candidate) || !isIsoTimestamp(request.observed_at) || !request.provenance || typeof request.provenance !== "object" || Array.isArray(request.provenance) || pending.first_transmit_at !== null && !isIsoTimestamp(pending.first_transmit_at) || pending.last_status_check_at !== null && !isIsoTimestamp(pending.last_status_check_at)) throw new Error(message);
19969
+ }
19970
+ return state;
19971
+ };
19868
19972
  assertNextState = (value) => {
19869
19973
  if (!value || typeof value !== "object" || Array.isArray(value)) {
19870
19974
  throw new Error("Agent-driven inference next recovery state is invalid.");
@@ -24019,15 +24123,17 @@ $items = @(Get-CimInstance Win32_Process | Where-Object { $_.Name -in @('node.ex
24019
24123
  });
24020
24124
 
24021
24125
  // lib/inference-host/copilot-adapter.ts
24126
+ import { randomUUID } from "node:crypto";
24022
24127
  import { mkdtemp as mkdtemp2, rm as rm4 } from "node:fs/promises";
24023
24128
  import { createRequire as createRequire2 } from "node:module";
24024
24129
  import { tmpdir as tmpdir3 } from "node:os";
24025
24130
  import { join as join5 } from "node:path";
24026
24131
  import {
24027
24132
  CopilotClient,
24028
- RuntimeConnection
24133
+ RuntimeConnection,
24134
+ defineTool
24029
24135
  } from "@github/copilot-sdk";
24030
- var COPILOT_SDK_VERSION, MAX_RESULT_BYTES, resolveRuntimePackage, copilotRuntimePackageCandidates, resolvePinnedCopilotCliPath, createPrivateWorkspace, cleanCopilotEnvironment, defaultClient, modelCapabilities, requiredUsageInteger, optionalUsageInteger, CopilotSubscriptionAdapter;
24136
+ var COPILOT_SDK_VERSION, MAX_RESULT_BYTES, resolveRuntimePackage, copilotRuntimePackageCandidates, resolvePinnedCopilotCliPath, createPrivateWorkspace, cleanCopilotEnvironment, defaultClient, modelCapabilities, requiredUsageInteger, optionalUsageInteger, copilotAgentToolDefinitions, CopilotSubscriptionAdapter;
24031
24137
  var init_copilot_adapter = __esm({
24032
24138
  "lib/inference-host/copilot-adapter.ts"() {
24033
24139
  "use strict";
@@ -24123,9 +24229,66 @@ var init_copilot_adapter = __esm({
24123
24229
  }
24124
24230
  return Number(value);
24125
24231
  };
24232
+ copilotAgentToolDefinitions = (input, evidence) => {
24233
+ const execute = async (tool, argumentsValue) => {
24234
+ const argumentsRecord = argumentsValue && typeof argumentsValue === "object" && !Array.isArray(argumentsValue) ? argumentsValue : {};
24235
+ const callId = randomUUID();
24236
+ const result2 = await input.executeTool({
24237
+ callId,
24238
+ tool,
24239
+ arguments: argumentsRecord
24240
+ });
24241
+ evidence.push({ callId, tool, arguments: argumentsRecord, success: result2.success });
24242
+ return result2.success ? result2.value : { error: result2.value };
24243
+ };
24244
+ return [
24245
+ defineTool("vtx_get_data", {
24246
+ description: "Request assignment-scoped VTX data using one exact canonical capability schema.",
24247
+ parameters: {
24248
+ oneOf: input.dataContract.map((descriptor) => ({
24249
+ type: "object",
24250
+ additionalProperties: false,
24251
+ required: ["capability", "arguments"],
24252
+ description: descriptor.description,
24253
+ properties: {
24254
+ capability: { type: "string", const: descriptor.id, title: descriptor.title },
24255
+ arguments: descriptor.input_schema
24256
+ }
24257
+ }))
24258
+ },
24259
+ skipPermission: true,
24260
+ handler: async (args) => await execute("vtx_get_data", args)
24261
+ }),
24262
+ defineTool("vtx_submit_decision", {
24263
+ description: "Submit one VTX structured trading decision candidate.",
24264
+ parameters: {
24265
+ type: "object",
24266
+ additionalProperties: false,
24267
+ required: ["candidate"],
24268
+ properties: { candidate: input.decisionSchema }
24269
+ },
24270
+ skipPermission: true,
24271
+ handler: async (args) => await execute("vtx_submit_decision", args)
24272
+ }),
24273
+ defineTool("vtx_decision_status", {
24274
+ description: "Resolve the durable status of a previously attempted decision operation.",
24275
+ parameters: {
24276
+ type: "object",
24277
+ additionalProperties: false,
24278
+ required: ["operation_id"],
24279
+ properties: { operation_id: { type: "string", minLength: 1 } }
24280
+ },
24281
+ skipPermission: true,
24282
+ handler: async (args) => await execute("vtx_decision_status", args)
24283
+ })
24284
+ ];
24285
+ };
24126
24286
  CopilotSubscriptionAdapter = class {
24127
24287
  constructor(dependencies = {}) {
24128
24288
  this.inFlight = /* @__PURE__ */ new Map();
24289
+ this.agentConnection = null;
24290
+ this.agentWorkspaceCleanups = /* @__PURE__ */ new Map();
24291
+ this.closing = false;
24129
24292
  this.dependencies = dependencies;
24130
24293
  }
24131
24294
  async preflight(signal) {
@@ -24337,6 +24500,224 @@ ${input.outputSchemaJson}`
24337
24500
  await workspace.cleanup();
24338
24501
  }
24339
24502
  }
24503
+ async runTurn(input) {
24504
+ if (this.closing) {
24505
+ throw new CodexAppServerError({
24506
+ message: "Copilot Agent adapter is closing.",
24507
+ category: "transport",
24508
+ code: "transport_closed",
24509
+ retryable: true
24510
+ });
24511
+ }
24512
+ input.signal?.throwIfAborted();
24513
+ if (Date.now() >= input.deadlineAtMs || input.dataContract.length === 0) {
24514
+ throw new CodexAppServerError({
24515
+ message: "Copilot Agent turn input is invalid or expired.",
24516
+ category: Date.now() >= input.deadlineAtMs ? "timeout" : "schema",
24517
+ code: Date.now() >= input.deadlineAtMs ? "deadline_exceeded" : "invalid_agent_turn_input",
24518
+ retryable: false
24519
+ });
24520
+ }
24521
+ const startedAt = (this.dependencies.now ?? Date.now)();
24522
+ const ownedWorkspace = input.durableThread ? null : await (this.dependencies.createAgentWorkspace ?? this.dependencies.createWorkspace ?? createPrivateWorkspace)();
24523
+ const workspacePath = input.durableThread?.threadPath ?? ownedWorkspace.path;
24524
+ if (ownedWorkspace) this.agentWorkspaceCleanups.set(workspacePath, ownedWorkspace.cleanup);
24525
+ const client = (this.dependencies.createClient ?? defaultClient)(workspacePath);
24526
+ let session = null;
24527
+ let durableCheckpointed = input.durableThread !== null;
24528
+ let dispatchEntered = false;
24529
+ const toolEvidence = [];
24530
+ try {
24531
+ await client.start();
24532
+ const tools = copilotAgentToolDefinitions(input, toolEvidence);
24533
+ const sessionConfig = {
24534
+ clientName: "@vtxmacro/cli durable Copilot Agent host",
24535
+ model: input.requestedModel,
24536
+ ...input.requestedReasoningEffort === "none" ? {} : { reasoningEffort: input.requestedReasoningEffort },
24537
+ systemMessage: {
24538
+ mode: "replace",
24539
+ content: `${input.systemPrompt}
24540
+
24541
+ Return only one JSON value matching this JSON Schema exactly:
24542
+ ${JSON.stringify(input.outputSchema)}`
24543
+ },
24544
+ tools,
24545
+ availableTools: tools.map((tool) => tool.name),
24546
+ enableConfigDiscovery: false,
24547
+ streaming: true,
24548
+ workingDirectory: workspacePath,
24549
+ infiniteSessions: { enabled: true }
24550
+ };
24551
+ session = input.durableThread ? await client.resumeSession(input.durableThread.threadId, sessionConfig) : await client.createSession(sessionConfig);
24552
+ this.agentConnection = {
24553
+ client,
24554
+ session,
24555
+ workspacePath,
24556
+ cleanup: ownedWorkspace?.cleanup ?? null
24557
+ };
24558
+ const thread = {
24559
+ threadId: session.sessionId,
24560
+ threadPath: workspacePath,
24561
+ effectiveModel: input.requestedModel,
24562
+ effectiveReasoningEffort: input.requestedReasoningEffort
24563
+ };
24564
+ if (input.onThreadReady) {
24565
+ await input.onThreadReady(thread);
24566
+ durableCheckpointed = true;
24567
+ }
24568
+ const usageEvents = [];
24569
+ session.on((event) => {
24570
+ if (event.type === "assistant.usage" && !event.agentId) usageEvents.push(event);
24571
+ });
24572
+ const onAbort = () => {
24573
+ void session?.abort().catch(() => void 0);
24574
+ };
24575
+ input.signal?.addEventListener("abort", onAbort, { once: true });
24576
+ dispatchEntered = true;
24577
+ let response;
24578
+ try {
24579
+ response = await session.sendAndWait(
24580
+ { prompt: input.userPrompt },
24581
+ Math.max(1, input.deadlineAtMs - Date.now())
24582
+ );
24583
+ } finally {
24584
+ input.signal?.removeEventListener("abort", onAbort);
24585
+ }
24586
+ if (!response?.data.content || Buffer.byteLength(response.data.content, "utf8") > MAX_RESULT_BYTES) {
24587
+ throw new Error("copilot_agent_invalid_result");
24588
+ }
24589
+ const observedModels = /* @__PURE__ */ new Set();
24590
+ const observedEfforts = /* @__PURE__ */ new Set();
24591
+ let inputTokens = 0;
24592
+ let cachedInputTokens = 0;
24593
+ let outputTokens = 0;
24594
+ let reasoningOutputTokens = 0;
24595
+ let cacheWriteInputTokens = 0;
24596
+ let cacheWriteSupported = false;
24597
+ let timeToFirstTokenMs = null;
24598
+ let providerCallId = null;
24599
+ for (const event of usageEvents) {
24600
+ if (event.type !== "assistant.usage") continue;
24601
+ observedModels.add(event.data.model);
24602
+ if (event.data.reasoningEffort) observedEfforts.add(event.data.reasoningEffort);
24603
+ inputTokens += requiredUsageInteger(event.data.inputTokens, "input_tokens");
24604
+ cachedInputTokens += optionalUsageInteger(event.data.cacheReadTokens, "cache_read_tokens");
24605
+ outputTokens += requiredUsageInteger(event.data.outputTokens, "output_tokens");
24606
+ reasoningOutputTokens += optionalUsageInteger(event.data.reasoningTokens, "reasoning_tokens");
24607
+ if (event.data.cacheWriteTokens !== void 0) {
24608
+ cacheWriteSupported = true;
24609
+ cacheWriteInputTokens += optionalUsageInteger(event.data.cacheWriteTokens, "cache_write_tokens");
24610
+ }
24611
+ if (timeToFirstTokenMs === null && Number.isSafeInteger(event.data.timeToFirstTokenMs)) {
24612
+ timeToFirstTokenMs = Number(event.data.timeToFirstTokenMs);
24613
+ }
24614
+ providerCallId = event.data.providerCallId ?? event.data.apiCallId ?? providerCallId;
24615
+ }
24616
+ if (usageEvents.length === 0) throw new Error("copilot_agent_usage_receipt_missing");
24617
+ if (response.data.model) observedModels.add(response.data.model);
24618
+ if (observedModels.size !== 1 || !observedModels.has(input.requestedModel)) {
24619
+ throw new Error("copilot_agent_effective_model_mismatch");
24620
+ }
24621
+ const effectiveEffort = input.requestedReasoningEffort === "none" ? observedEfforts.size === 0 ? "none" : observedEfforts.size === 1 ? [...observedEfforts][0] : null : observedEfforts.size === 1 ? [...observedEfforts][0] : null;
24622
+ if (effectiveEffort !== input.requestedReasoningEffort) {
24623
+ throw new Error("copilot_agent_effective_effort_mismatch");
24624
+ }
24625
+ const usage = {
24626
+ inputTokens,
24627
+ cachedInputTokens: Math.min(cachedInputTokens, inputTokens),
24628
+ outputTokens,
24629
+ reasoningOutputTokens: Math.min(reasoningOutputTokens, outputTokens),
24630
+ totalTokens: inputTokens + outputTokens,
24631
+ cacheWriteInputTokens: cacheWriteSupported ? cacheWriteInputTokens : null,
24632
+ cacheWriteSupported
24633
+ };
24634
+ return {
24635
+ thread,
24636
+ turn: {
24637
+ text: response.data.content,
24638
+ reasoningContent: response.data.reasoningText ?? null,
24639
+ reasoningSummary: null,
24640
+ requestedModel: input.requestedModel,
24641
+ effectiveModel: input.requestedModel,
24642
+ requestedReasoningEffort: input.requestedReasoningEffort,
24643
+ effectiveReasoningEffort: effectiveEffort,
24644
+ adapterRequestId: providerCallId ?? response.data.requestId ?? response.data.messageId,
24645
+ adapterResponseId: response.data.serviceRequestId ?? response.data.messageId,
24646
+ usage,
24647
+ latencyMs: Math.max(0, (this.dependencies.now ?? Date.now)() - startedAt),
24648
+ timeToFirstTokenMs,
24649
+ terminalStatus: "completed",
24650
+ toolCalls: toolEvidence,
24651
+ webSearches: []
24652
+ }
24653
+ };
24654
+ } catch (error48) {
24655
+ if (error48 instanceof CodexAppServerError) throw error48;
24656
+ const cancelled = input.signal?.aborted === true;
24657
+ const deadline = Date.now() >= input.deadlineAtMs;
24658
+ if (dispatchEntered) await session?.abort().catch(() => void 0);
24659
+ if (session && !durableCheckpointed) {
24660
+ const abandonedSession = session;
24661
+ session = null;
24662
+ if (this.agentConnection?.session === abandonedSession) this.agentConnection = null;
24663
+ await abandonedSession.disconnect().catch(() => void 0);
24664
+ await client.deleteSession(abandonedSession.sessionId).catch(() => void 0);
24665
+ const cleanup = this.agentWorkspaceCleanups.get(workspacePath);
24666
+ if (cleanup) {
24667
+ await cleanup().catch(() => void 0);
24668
+ this.agentWorkspaceCleanups.delete(workspacePath);
24669
+ }
24670
+ }
24671
+ throw new CodexAppServerError({
24672
+ message: cancelled ? "Copilot Agent turn was cancelled." : deadline ? "Copilot Agent turn exceeded its deadline." : error48 instanceof Error ? error48.message : "Copilot Agent adapter failed.",
24673
+ category: cancelled ? "cancelled" : deadline ? "timeout" : "adapter",
24674
+ code: cancelled ? "cancelled" : deadline ? "deadline_exceeded" : "copilot_agent_adapter_failure",
24675
+ retryable: false,
24676
+ dispatchOutcome: dispatchEntered ? "confirmed_dispatched" : "not_dispatched",
24677
+ cause: error48
24678
+ });
24679
+ } finally {
24680
+ await session?.disconnect().catch(() => void 0);
24681
+ await client.stop().catch(async () => {
24682
+ await client.forceStop();
24683
+ });
24684
+ if (this.agentConnection?.session === session) this.agentConnection = null;
24685
+ }
24686
+ }
24687
+ async releaseThread(thread) {
24688
+ const active = this.agentConnection;
24689
+ if (active?.session.sessionId === thread.threadId) {
24690
+ await active.session.disconnect().catch(() => void 0);
24691
+ await active.client.stop().catch(async () => {
24692
+ await active.client.forceStop();
24693
+ });
24694
+ this.agentConnection = null;
24695
+ }
24696
+ const client = (this.dependencies.createClient ?? defaultClient)(thread.threadPath);
24697
+ await client.start();
24698
+ try {
24699
+ await client.deleteSession(thread.threadId);
24700
+ } finally {
24701
+ await client.stop().catch(async () => {
24702
+ await client.forceStop();
24703
+ });
24704
+ }
24705
+ const cleanup = this.agentWorkspaceCleanups.get(thread.threadPath);
24706
+ if (cleanup) {
24707
+ await cleanup();
24708
+ this.agentWorkspaceCleanups.delete(thread.threadPath);
24709
+ }
24710
+ }
24711
+ async close() {
24712
+ this.closing = true;
24713
+ const active = this.agentConnection;
24714
+ this.agentConnection = null;
24715
+ if (!active) return;
24716
+ await active.session.disconnect().catch(() => void 0);
24717
+ await active.client.stop().catch(async () => {
24718
+ await active.client.forceStop();
24719
+ });
24720
+ }
24340
24721
  };
24341
24722
  }
24342
24723
  });
@@ -30914,7 +31295,7 @@ var require_ajv = __commonJS({
30914
31295
  });
30915
31296
 
30916
31297
  // lib/inference-host/runner.ts
30917
- import { createHash as createHash6, randomUUID } from "node:crypto";
31298
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
30918
31299
  function createDefaultInferenceHostRunnerDependencies(options) {
30919
31300
  const fetchImpl = options.fetchImpl ?? fetch;
30920
31301
  return {
@@ -30949,7 +31330,7 @@ function createDefaultInferenceHostRunnerDependencies(options) {
30949
31330
  envelopePublicKey: options.envelopePublicKey
30950
31331
  };
30951
31332
  }
30952
- var import_ajv, RUNTIME_RECEIPT_SCHEMA_VERSION, ATTEMPT_RECEIPT_SCHEMA_VERSION, DEFAULT_ADVERTISEMENT_TTL_MS, DEFAULT_ADVERTISEMENT_REFRESH_LEAD_MS, DEFAULT_HOST_HEARTBEAT_MS, DEFAULT_PROVIDER_RATE_LIMIT_REFRESH_MS, DEFAULT_ATTEMPT_HEARTBEAT_MS, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_REMOTE_RETRY_LIMIT, MIN_SLEEP_MS, MIN_HOST_HEARTBEAT_GAP_DIAGNOSTIC_MS, HOST_HEARTBEAT_GAP_DIAGNOSTIC_FACTOR, DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS, MIN_CLAIM_START_WINDOW_MS, MAX_ATTEMPT_START_RETRY_DELAY_MS, UNBOUNDED_AVAILABLE_SLOTS, buildInferenceAdvertisedModels, summarizeInferenceHostRuntimeRecovery, FileInferenceHostRuntimeReceiptStore, InferenceHostRecoveryRequiredError, InferenceHostRunnerError, defaultSleep, abortError, settlesWithin, defaultRegisterSignalHandlers, safeInteger, exactObjectKeys, validIso, validateAttemptReceipt, validateRuntimeReceipt, sha256, stableOperationId, buildAttemptStartRequest, isoAt, providerWeeklyQuotaFromRateLimits, finitePositiveOption, validateRunnerOptions, assertLocalCredentialIdentity, retryableRemoteError, remoteRetryAfterMs, controlPlaneFatal, defaultValidateOutput, objectRecord, positiveUtf8ByteLimit, assertCompilableOutputSchema, parseOutputContract, membershipFailureDisposition, safeFailureCode, attemptStartRetryFallbackMs, exactJson2, runnerIdentityMismatch, assertAdvertisementResult, assertHostHeartbeatResult, assertClaimResult, assertStartResult, assertJobHeartbeatResult, assertTerminalResult, classifyFailure, ambiguousHeartbeatTransport, reportedTokenUsage, reportedUsage, InferenceHostRunner, createCodexAgentControlClient, CODEX_AGENT_SYSTEM_PROMPT, CODEX_AGENT_WAKE_SCHEMA, parseCodexAgentWake, CodexAgentRuntime;
31333
+ var import_ajv, RUNTIME_RECEIPT_SCHEMA_VERSION, ATTEMPT_RECEIPT_SCHEMA_VERSION, DEFAULT_ADVERTISEMENT_TTL_MS, DEFAULT_ADVERTISEMENT_REFRESH_LEAD_MS, DEFAULT_HOST_HEARTBEAT_MS, DEFAULT_PROVIDER_RATE_LIMIT_REFRESH_MS, DEFAULT_ATTEMPT_HEARTBEAT_MS, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_REMOTE_RETRY_LIMIT, MIN_SLEEP_MS, MIN_HOST_HEARTBEAT_GAP_DIAGNOSTIC_MS, HOST_HEARTBEAT_GAP_DIAGNOSTIC_FACTOR, DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS, MIN_CLAIM_START_WINDOW_MS, MAX_ATTEMPT_START_RETRY_DELAY_MS, UNBOUNDED_AVAILABLE_SLOTS, buildInferenceAdvertisedModels, summarizeInferenceHostRuntimeRecovery, FileInferenceHostRuntimeReceiptStore, InferenceHostRecoveryRequiredError, InferenceHostRunnerError, defaultSleep, abortError, settlesWithin, defaultRegisterSignalHandlers, safeInteger, exactObjectKeys, validIso, validateAttemptReceipt, validateRuntimeReceipt, sha256, stableOperationId, buildAttemptStartRequest, isoAt, providerWeeklyQuotaFromRateLimits, finitePositiveOption, validateRunnerOptions, assertLocalCredentialIdentity, retryableRemoteError, remoteRetryAfterMs, controlPlaneFatal, defaultValidateOutput, objectRecord, positiveUtf8ByteLimit, assertCompilableOutputSchema, parseOutputContract, membershipFailureDisposition, safeFailureCode, attemptStartRetryFallbackMs, exactJson2, runnerIdentityMismatch, assertAdvertisementResult, assertHostHeartbeatResult, assertClaimResult, assertStartResult, assertJobHeartbeatResult, assertTerminalResult, classifyFailure, ambiguousHeartbeatTransport, reportedTokenUsage, reportedUsage, InferenceHostRunner, createInferenceAgentControlClient, INFERENCE_AGENT_SYSTEM_PROMPT, INFERENCE_AGENT_WAKE_SCHEMA, parseInferenceAgentWake, InferenceAgentRuntime;
30953
31334
  var init_runner = __esm({
30954
31335
  "lib/inference-host/runner.ts"() {
30955
31336
  "use strict";
@@ -31333,7 +31714,7 @@ var init_runner = __esm({
31333
31714
  adapterId,
31334
31715
  options.structuredOutput ?? adapterId === "codex",
31335
31716
  options.sameAttemptRecovery ?? adapterId === "codex",
31336
- options.agentRuntime && adapterId === "codex" ? ["provider", "agent"] : ["provider"]
31717
+ options.agentRuntime ? ["provider", "agent"] : ["provider"]
31337
31718
  ),
31338
31719
  maxConcurrency: options.maxConcurrency === null || options.maxConcurrency === void 0 ? null : finitePositiveOption(options.maxConcurrency, 1, "Maximum concurrency"),
31339
31720
  advertisementTtlMs,
@@ -31788,10 +32169,11 @@ var init_runner = __esm({
31788
32169
  const agentRuntimeSettings = settings.agentRuntime;
31789
32170
  agentLoop = (async () => {
31790
32171
  while (!agentAbort.signal.aborted) {
31791
- const agentRuntime = new CodexAgentRuntime({
32172
+ const agentRuntime = new InferenceAgentRuntime({
32173
+ adapterId: settings.adapterId,
31792
32174
  hostId: localState.host_id,
31793
32175
  statePath: agentRuntimeSettings.statePath,
31794
- controlClient: createCodexAgentControlClient(mcp),
32176
+ controlClient: createInferenceAgentControlClient(mcp),
31795
32177
  adapter: agentRuntimeSettings.adapter
31796
32178
  });
31797
32179
  try {
@@ -33129,7 +33511,7 @@ var init_runner = __esm({
33129
33511
  }
33130
33512
  }
33131
33513
  };
33132
- createCodexAgentControlClient = (mcp) => ({
33514
+ createInferenceAgentControlClient = (mcp) => ({
33133
33515
  nextAssignment: async (request, options) => {
33134
33516
  const result2 = await mcp.callTool("inference.agent.assignment.next", {
33135
33517
  ...request,
@@ -33172,9 +33554,9 @@ var init_runner = __esm({
33172
33554
  await mcp.callTool("inference.agent.assignment.release", request, options);
33173
33555
  }
33174
33556
  });
33175
- CODEX_AGENT_SYSTEM_PROMPT = `You autonomously control one running VTX bot.
33557
+ INFERENCE_AGENT_SYSTEM_PROMPT = `You autonomously control one running VTX bot.
33176
33558
  VTX supplies no trading prompt or prepared market context. At your own cadence, request zero, some, or all assignment-scoped data with vtx_get_data and independently use web search when useful. Submit trading instructions only through vtx_submit_decision, using the assignment's exact decision schema. VTX remains responsible for validating and executing the same structured decision used by normal Server and Client modes. You have no shell, filesystem-write, app, plugin, browser-control, or subagent authority.`;
33177
- CODEX_AGENT_WAKE_SCHEMA = {
33559
+ INFERENCE_AGENT_WAKE_SCHEMA = {
33178
33560
  type: "object",
33179
33561
  additionalProperties: false,
33180
33562
  required: ["next_wake_seconds", "summary"],
@@ -33183,29 +33565,29 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33183
33565
  summary: { type: "string", minLength: 1, maxLength: 2e3 }
33184
33566
  }
33185
33567
  };
33186
- parseCodexAgentWake = (text, assignment) => {
33568
+ parseInferenceAgentWake = (text, assignment) => {
33187
33569
  let value;
33188
33570
  try {
33189
33571
  value = JSON.parse(text);
33190
33572
  } catch {
33191
33573
  throw new InferenceHostRunnerError(
33192
33574
  "invalid_agent_wake",
33193
- "Codex Agent returned invalid wake JSON."
33575
+ "Inference Agent returned invalid wake JSON."
33194
33576
  );
33195
33577
  }
33196
33578
  if (!value || typeof value !== "object" || Array.isArray(value)) {
33197
- throw new InferenceHostRunnerError("invalid_agent_wake", "Codex Agent wake is invalid.");
33579
+ throw new InferenceHostRunnerError("invalid_agent_wake", "Inference Agent wake is invalid.");
33198
33580
  }
33199
33581
  const seconds = value.next_wake_seconds;
33200
33582
  if (!Number.isSafeInteger(seconds) || Number(seconds) < 1) {
33201
- throw new InferenceHostRunnerError("invalid_agent_wake", "Codex Agent wake is invalid.");
33583
+ throw new InferenceHostRunnerError("invalid_agent_wake", "Inference Agent wake is invalid.");
33202
33584
  }
33203
33585
  return Math.max(
33204
33586
  assignment.minimum_wake_seconds,
33205
33587
  Math.min(assignment.maximum_wake_seconds, Number(seconds))
33206
33588
  );
33207
33589
  };
33208
- CodexAgentRuntime = class {
33590
+ InferenceAgentRuntime = class {
33209
33591
  constructor(options) {
33210
33592
  this.options = options;
33211
33593
  this.stopped = false;
@@ -33239,29 +33621,55 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33239
33621
  }
33240
33622
  async runOnce(signal) {
33241
33623
  const nowIso = () => new Date(this.now()).toISOString();
33242
- const recoveredState = await readCodexAgentRuntimeState(this.options.statePath);
33624
+ const recoveredState = await readInferenceAgentRuntimeState(this.options.statePath);
33243
33625
  if (recoveredState && recoveredState.host_id !== this.options.hostId) {
33244
33626
  throw new InferenceHostRunnerError(
33245
33627
  "agent_recovery_scope_mismatch",
33246
- "Codex Agent recovery state belongs to another host."
33628
+ "Inference Agent recovery state belongs to another host."
33247
33629
  );
33248
33630
  }
33249
33631
  let state;
33250
33632
  if (recoveredState?.schema_version === "vtx_codex_agent_runtime_v1") {
33633
+ if (this.options.adapterId !== "codex") {
33634
+ throw new InferenceHostRunnerError(
33635
+ "agent_recovery_adapter_mismatch",
33636
+ "Legacy Codex Agent recovery state cannot be opened by another adapter."
33637
+ );
33638
+ }
33251
33639
  state = await this.upgradeLegacyRuntimeState(recoveredState, signal);
33252
33640
  if (!state) return this.now() + (this.options.idlePollMs ?? 5e3);
33641
+ } else if (recoveredState?.schema_version === "vtx_codex_agent_runtime_v2") {
33642
+ if (this.options.adapterId !== "codex") {
33643
+ throw new InferenceHostRunnerError(
33644
+ "agent_recovery_adapter_mismatch",
33645
+ "Legacy Codex Agent recovery state cannot be opened by another adapter."
33646
+ );
33647
+ }
33648
+ state = {
33649
+ ...recoveredState,
33650
+ schema_version: "vtx_inference_agent_runtime_v3",
33651
+ adapter_id: "codex"
33652
+ };
33653
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33253
33654
  } else {
33254
33655
  state = recoveredState ?? null;
33656
+ if (state && state.adapter_id !== this.options.adapterId) {
33657
+ throw new InferenceHostRunnerError(
33658
+ "agent_recovery_adapter_mismatch",
33659
+ "Inference Agent recovery state belongs to another adapter."
33660
+ );
33661
+ }
33255
33662
  }
33256
33663
  if (!state) {
33257
33664
  const assignment2 = await this.options.controlClient.nextAssignment({
33258
- operation_id: randomUUID(),
33665
+ operation_id: randomUUID2(),
33259
33666
  host_id: this.options.hostId,
33260
33667
  requested_at: nowIso()
33261
33668
  }, { signal });
33262
33669
  if (!assignment2) return this.now() + (this.options.idlePollMs ?? 5e3);
33263
33670
  state = {
33264
- schema_version: "vtx_codex_agent_runtime_v2",
33671
+ schema_version: "vtx_inference_agent_runtime_v3",
33672
+ adapter_id: this.options.adapterId,
33265
33673
  host_id: this.options.hostId,
33266
33674
  assignment: assignment2,
33267
33675
  thread: null,
@@ -33269,7 +33677,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33269
33677
  pending_decision: null,
33270
33678
  updated_at: nowIso()
33271
33679
  };
33272
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33680
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33273
33681
  } else {
33274
33682
  const persistedWakeAtMs = Date.parse(state.next_wake_at);
33275
33683
  if (persistedWakeAtMs > this.now()) {
@@ -33277,8 +33685,8 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33277
33685
  if (wakeOutcome !== "ready") {
33278
33686
  return this.now() + (this.options.idlePollMs ?? 5e3);
33279
33687
  }
33280
- const rereadState = await readCodexAgentRuntimeState(this.options.statePath);
33281
- if (rereadState?.schema_version !== "vtx_codex_agent_runtime_v2") {
33688
+ const rereadState = await readInferenceAgentRuntimeState(this.options.statePath);
33689
+ if (rereadState?.schema_version !== "vtx_inference_agent_runtime_v3" || rereadState.adapter_id !== this.options.adapterId) {
33282
33690
  return this.now() + (this.options.idlePollMs ?? 5e3);
33283
33691
  }
33284
33692
  state = rereadState;
@@ -33287,7 +33695,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33287
33695
  state = await this.resolvePendingDecision(state, signal);
33288
33696
  let assignment = state.assignment;
33289
33697
  const preTurnHeartbeat = await this.options.controlClient.heartbeat({
33290
- operation_id: randomUUID(),
33698
+ operation_id: randomUUID2(),
33291
33699
  host_id: this.options.hostId,
33292
33700
  assignment_id: assignment.assignment_id,
33293
33701
  assignment_generation: assignment.assignment_generation,
@@ -33302,7 +33710,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33302
33710
  lease_expires_at: preTurnHeartbeat.lease_expires_at
33303
33711
  };
33304
33712
  state = { ...state, assignment, updated_at: nowIso() };
33305
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33713
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33306
33714
  const turnAbort = new AbortController();
33307
33715
  const relayAbort = () => turnAbort.abort();
33308
33716
  signal?.addEventListener("abort", relayAbort, { once: true });
@@ -33312,7 +33720,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33312
33720
  const heartbeatTask = (async () => {
33313
33721
  while (!heartbeatStopped && !turnAbort.signal.aborted) {
33314
33722
  const heartbeat = await this.options.controlClient.heartbeat({
33315
- operation_id: randomUUID(),
33723
+ operation_id: randomUUID2(),
33316
33724
  host_id: this.options.hostId,
33317
33725
  assignment_id: assignment.assignment_id,
33318
33726
  assignment_generation: assignment.assignment_generation,
@@ -33336,7 +33744,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33336
33744
  const deadlineAtMs = this.now() + (this.options.turnTimeoutMs ?? 10 * 6e4);
33337
33745
  const result2 = await this.options.adapter.runTurn({
33338
33746
  durableThread: state.thread,
33339
- systemPrompt: CODEX_AGENT_SYSTEM_PROMPT,
33747
+ systemPrompt: INFERENCE_AGENT_SYSTEM_PROMPT,
33340
33748
  userPrompt: JSON.stringify({
33341
33749
  assignment_id: assignment.assignment_id,
33342
33750
  assignment_generation: assignment.assignment_generation,
@@ -33350,7 +33758,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33350
33758
  maximum: assignment.maximum_wake_seconds
33351
33759
  }
33352
33760
  }),
33353
- outputSchema: CODEX_AGENT_WAKE_SCHEMA,
33761
+ outputSchema: INFERENCE_AGENT_WAKE_SCHEMA,
33354
33762
  dataContract: assignment.data_contract,
33355
33763
  decisionSchema: assignment.output_schema,
33356
33764
  requestedModel: assignment.model_id,
@@ -33359,7 +33767,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33359
33767
  signal: turnAbort.signal,
33360
33768
  onThreadReady: async (thread) => {
33361
33769
  state = { ...state, thread, updated_at: nowIso() };
33362
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33770
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33363
33771
  },
33364
33772
  executeTool: async (call) => {
33365
33773
  if (call.tool === "vtx_get_data") {
@@ -33369,7 +33777,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33369
33777
  return { success: false, value: { error: "invalid_data_request" } };
33370
33778
  }
33371
33779
  const value = await this.options.controlClient.dataCall({
33372
- operation_id: randomUUID(),
33780
+ operation_id: randomUUID2(),
33373
33781
  host_id: this.options.hostId,
33374
33782
  assignment_id: assignment.assignment_id,
33375
33783
  assignment_generation: assignment.assignment_generation,
@@ -33393,12 +33801,12 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33393
33801
  return { success: false, value: { error: "decision_outcome_unresolved" } };
33394
33802
  }
33395
33803
  const pending = {
33396
- operation_id: randomUUID(),
33804
+ operation_id: randomUUID2(),
33397
33805
  candidate,
33398
33806
  observed_at: nowIso(),
33399
33807
  provenance: {
33400
- source: "codex_agent",
33401
- codex_thread_id: liveThread.threadId,
33808
+ source: "external_agent",
33809
+ agent_run_id: liveThread.threadId,
33402
33810
  requested_model: assignment.model_id,
33403
33811
  effective_model: liveThread.effectiveModel,
33404
33812
  requested_reasoning_effort: assignment.reasoning_effort,
@@ -33408,13 +33816,13 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33408
33816
  last_status_check_at: null
33409
33817
  };
33410
33818
  state = { ...state, pending_decision: pending, updated_at: nowIso() };
33411
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33819
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33412
33820
  state = {
33413
33821
  ...state,
33414
33822
  pending_decision: { ...pending, first_transmit_at: nowIso() },
33415
33823
  updated_at: nowIso()
33416
33824
  };
33417
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33825
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33418
33826
  let submitStatus;
33419
33827
  try {
33420
33828
  submitStatus = await this.options.controlClient.submitDecision({
@@ -33431,7 +33839,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33431
33839
  }
33432
33840
  if (submitStatus.status === "applied" || submitStatus.status === "not_applied") {
33433
33841
  state = { ...state, pending_decision: null, updated_at: nowIso() };
33434
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33842
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33435
33843
  }
33436
33844
  return {
33437
33845
  success: submitStatus.status === "applied",
@@ -33455,7 +33863,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33455
33863
  pending_decision: null,
33456
33864
  updated_at: nowIso()
33457
33865
  };
33458
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33866
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33459
33867
  }
33460
33868
  return {
33461
33869
  success: status.status === "applied" || status.status === "not_applied",
@@ -33463,11 +33871,11 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33463
33871
  };
33464
33872
  }
33465
33873
  });
33466
- const wakeSeconds = parseCodexAgentWake(result2.turn.text, assignment);
33874
+ const wakeSeconds = parseInferenceAgentWake(result2.turn.text, assignment);
33467
33875
  const nextWakeAtMs = this.now() + wakeSeconds * 1e3;
33468
33876
  const scheduledWakeAt = new Date(nextWakeAtMs).toISOString();
33469
33877
  const wakeHeartbeat = await this.options.controlClient.heartbeat({
33470
- operation_id: randomUUID(),
33878
+ operation_id: randomUUID2(),
33471
33879
  host_id: this.options.hostId,
33472
33880
  assignment_id: assignment.assignment_id,
33473
33881
  assignment_generation: assignment.assignment_generation,
@@ -33477,7 +33885,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33477
33885
  if (wakeHeartbeat.directive === "cancel") {
33478
33886
  if (state.pending_decision) return this.now() + (this.options.idlePollMs ?? 5e3);
33479
33887
  await this.options.adapter.releaseThread(result2.thread).catch(() => void 0);
33480
- await clearCodexAgentRuntimeState(this.options.statePath);
33888
+ await clearInferenceAgentRuntimeState(this.options.statePath);
33481
33889
  return this.now() + (this.options.idlePollMs ?? 5e3);
33482
33890
  }
33483
33891
  latestLeaseExpiry = wakeHeartbeat.lease_expires_at;
@@ -33488,7 +33896,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33488
33896
  next_wake_at: scheduledWakeAt,
33489
33897
  updated_at: nowIso()
33490
33898
  };
33491
- await writeCodexAgentRuntimeState(this.options.statePath, state);
33899
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
33492
33900
  return nextWakeAtMs;
33493
33901
  } catch (error48) {
33494
33902
  if (!cancelled) throw error48;
@@ -33507,7 +33915,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33507
33915
  if (state.thread) {
33508
33916
  await this.options.adapter.releaseThread(state.thread).catch(() => void 0);
33509
33917
  }
33510
- await clearCodexAgentRuntimeState(this.options.statePath);
33918
+ await clearInferenceAgentRuntimeState(this.options.statePath);
33511
33919
  return null;
33512
33920
  }
33513
33921
  async resolveLegacyPendingDecision(state, signal) {
@@ -33519,7 +33927,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33519
33927
  pending_decision: { ...pending, last_status_check_at: checkedAt },
33520
33928
  updated_at: checkedAt
33521
33929
  };
33522
- await writeCodexAgentRuntimeState(this.options.statePath, checking);
33930
+ await writeInferenceAgentRuntimeState(this.options.statePath, checking);
33523
33931
  const status = await this.options.controlClient.decisionStatus({
33524
33932
  host_id: this.options.hostId,
33525
33933
  assignment_id: state.assignment.assignment_id,
@@ -33532,7 +33940,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33532
33940
  pending_decision: null,
33533
33941
  updated_at: new Date(this.now()).toISOString()
33534
33942
  };
33535
- await writeCodexAgentRuntimeState(this.options.statePath, resolved);
33943
+ await writeInferenceAgentRuntimeState(this.options.statePath, resolved);
33536
33944
  return resolved;
33537
33945
  }
33538
33946
  async resolvePendingDecision(state, signal) {
@@ -33544,7 +33952,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33544
33952
  pending_decision: { ...pending, last_status_check_at: checkedAt },
33545
33953
  updated_at: checkedAt
33546
33954
  };
33547
- await writeCodexAgentRuntimeState(this.options.statePath, checking);
33955
+ await writeInferenceAgentRuntimeState(this.options.statePath, checking);
33548
33956
  const status = await this.options.controlClient.decisionStatus({
33549
33957
  host_id: this.options.hostId,
33550
33958
  assignment_id: state.assignment.assignment_id,
@@ -33557,7 +33965,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33557
33965
  pending_decision: null,
33558
33966
  updated_at: new Date(this.now()).toISOString()
33559
33967
  };
33560
- await writeCodexAgentRuntimeState(this.options.statePath, resolved);
33968
+ await writeInferenceAgentRuntimeState(this.options.statePath, resolved);
33561
33969
  return resolved;
33562
33970
  }
33563
33971
  async handleCancelledAssignment(state, signal) {
@@ -33573,7 +33981,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33573
33981
  if (current.thread) {
33574
33982
  await this.options.adapter.releaseThread(current.thread).catch(() => void 0);
33575
33983
  }
33576
- await clearCodexAgentRuntimeState(this.options.statePath);
33984
+ await clearInferenceAgentRuntimeState(this.options.statePath);
33577
33985
  }
33578
33986
  async waitUntilWake(nextWakeAtMs, signal) {
33579
33987
  const heartbeatIntervalMs = this.options.heartbeatIntervalMs ?? 3e3;
@@ -33581,11 +33989,11 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33581
33989
  await this.sleep(Math.min(heartbeatIntervalMs, nextWakeAtMs - this.now()), signal);
33582
33990
  if (this.stopped || signal?.aborted) return "stopped";
33583
33991
  if (this.now() >= nextWakeAtMs) return "ready";
33584
- const state = await readCodexAgentRuntimeState(this.options.statePath);
33585
- if (state?.schema_version !== "vtx_codex_agent_runtime_v2") return "cancelled";
33992
+ const state = await readInferenceAgentRuntimeState(this.options.statePath);
33993
+ if (state?.schema_version !== "vtx_inference_agent_runtime_v3" || state.adapter_id !== this.options.adapterId) return "cancelled";
33586
33994
  const requestedAt = new Date(this.now()).toISOString();
33587
33995
  const heartbeat = await this.options.controlClient.heartbeat({
33588
- operation_id: randomUUID(),
33996
+ operation_id: randomUUID2(),
33589
33997
  host_id: this.options.hostId,
33590
33998
  assignment_id: state.assignment.assignment_id,
33591
33999
  assignment_generation: state.assignment.assignment_generation,
@@ -33595,7 +34003,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33595
34003
  await this.handleCancelledAssignment(state, signal);
33596
34004
  return "cancelled";
33597
34005
  }
33598
- await writeCodexAgentRuntimeState(this.options.statePath, {
34006
+ await writeInferenceAgentRuntimeState(this.options.statePath, {
33599
34007
  ...state,
33600
34008
  assignment: {
33601
34009
  ...state.assignment,
@@ -33607,8 +34015,24 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33607
34015
  return this.stopped || signal?.aborted ? "stopped" : "ready";
33608
34016
  }
33609
34017
  async releaseForHostStop() {
33610
- let state = await readCodexAgentRuntimeState(this.options.statePath);
33611
- if (!state) return;
34018
+ const recovered = await readInferenceAgentRuntimeState(this.options.statePath);
34019
+ if (!recovered) return;
34020
+ let state;
34021
+ if (recovered.schema_version === "vtx_codex_agent_runtime_v1") {
34022
+ if (this.options.adapterId !== "codex") return;
34023
+ state = recovered;
34024
+ } else if (recovered.schema_version === "vtx_codex_agent_runtime_v2") {
34025
+ if (this.options.adapterId !== "codex") return;
34026
+ state = {
34027
+ ...recovered,
34028
+ schema_version: "vtx_inference_agent_runtime_v3",
34029
+ adapter_id: "codex"
34030
+ };
34031
+ await writeInferenceAgentRuntimeState(this.options.statePath, state);
34032
+ } else {
34033
+ if (recovered.adapter_id !== this.options.adapterId) return;
34034
+ state = recovered;
34035
+ }
33612
34036
  try {
33613
34037
  state = state.schema_version === "vtx_codex_agent_runtime_v1" ? await this.resolveLegacyPendingDecision(state) : await this.resolvePendingDecision(state);
33614
34038
  } catch {
@@ -33616,7 +34040,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33616
34040
  }
33617
34041
  if (state.pending_decision) return;
33618
34042
  await this.options.controlClient.releaseAssignment({
33619
- operation_id: randomUUID(),
34043
+ operation_id: randomUUID2(),
33620
34044
  host_id: this.options.hostId,
33621
34045
  assignment_id: state.assignment.assignment_id,
33622
34046
  assignment_generation: state.assignment.assignment_generation,
@@ -33624,7 +34048,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33624
34048
  requested_at: new Date(this.now()).toISOString()
33625
34049
  }, {});
33626
34050
  if (state.thread) await this.options.adapter.releaseThread(state.thread);
33627
- await clearCodexAgentRuntimeState(this.options.statePath);
34051
+ await clearInferenceAgentRuntimeState(this.options.statePath);
33628
34052
  }
33629
34053
  };
33630
34054
  }
@@ -33632,7 +34056,7 @@ VTX supplies no trading prompt or prepared market context. At your own cadence,
33632
34056
 
33633
34057
  // lib/inference-host/service.ts
33634
34058
  import { spawn as spawn6 } from "node:child_process";
33635
- import { randomUUID as randomUUID2 } from "node:crypto";
34059
+ import { randomUUID as randomUUID3 } from "node:crypto";
33636
34060
  import { createWriteStream, readFileSync } from "node:fs";
33637
34061
  import { access as access3, chmod as chmod3, mkdir as mkdir3, readFile as readFile5, rm as rm5, writeFile as writeFile2 } from "node:fs/promises";
33638
34062
  import { homedir as homedir2 } from "node:os";
@@ -34157,7 +34581,7 @@ WantedBy=default.target
34157
34581
  const previousRuntimeUpdatedAt = previousRuntime?.manifest_generation === manifest.generation ? Date.parse(previousRuntime.updated_at) : Number.NaN;
34158
34582
  const restoredManifest = preserveGeneration ? manifest : assertManifest({
34159
34583
  ...manifest,
34160
- generation: randomUUID2(),
34584
+ generation: randomUUID3(),
34161
34585
  installed_at: this.now().toISOString()
34162
34586
  });
34163
34587
  await writeAtomicInferencePrivateFile(
@@ -34443,7 +34867,7 @@ ${cleanup.stderr}`)) {
34443
34867
  ].sort((left, right) => left.instance_name.localeCompare(right.instance_name));
34444
34868
  const manifest = assertManifest({
34445
34869
  schema_version: "vtx_inference_service_v3",
34446
- generation: randomUUID2(),
34870
+ generation: randomUUID3(),
34447
34871
  installed_at: this.now().toISOString(),
34448
34872
  executable: this.executable,
34449
34873
  script: this.script,
@@ -34657,7 +35081,7 @@ ${result2.stderr}`)) {
34657
35081
  if (remaining.length === 0) return await this.uninstallUnlocked();
34658
35082
  return await this.replaceManifestUnlocked({
34659
35083
  ...manifest,
34660
- generation: randomUUID2(),
35084
+ generation: randomUUID3(),
34661
35085
  installed_at: this.now().toISOString(),
34662
35086
  workers: remaining
34663
35087
  }, await readInferenceHostServiceDesired(this.desiredPath()));
@@ -35047,7 +35471,7 @@ __export(cli_exports, {
35047
35471
  registerInferenceHostServiceControlInput: () => registerInferenceHostServiceControlInput,
35048
35472
  runInferenceHostCli: () => runInferenceHostCli
35049
35473
  });
35050
- import { createHash as createHash7, randomUUID as randomUUID3 } from "node:crypto";
35474
+ import { createHash as createHash7, randomUUID as randomUUID4 } from "node:crypto";
35051
35475
  import { spawn as spawn7 } from "node:child_process";
35052
35476
  import { lstat as lstat4, realpath as realpath4, rm as rm6 } from "node:fs/promises";
35053
35477
  import { hostname as osHostname } from "node:os";
@@ -35111,6 +35535,42 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
35111
35535
  async () => await agentFail(config2, parsed, dependencies, warnings)
35112
35536
  );
35113
35537
  }
35538
+ if (parsed.command === "agent-assignment-next") {
35539
+ return await withAgentCommandLock(
35540
+ config2,
35541
+ async () => await foregroundAssignmentNext(config2, parsed, dependencies, warnings)
35542
+ );
35543
+ }
35544
+ if (parsed.command === "agent-assignment-heartbeat") {
35545
+ return await withAgentCommandLock(
35546
+ config2,
35547
+ async () => await foregroundAssignmentHeartbeat(config2, parsed, dependencies, warnings)
35548
+ );
35549
+ }
35550
+ if (parsed.command === "agent-data-call") {
35551
+ return await withAgentCommandLock(
35552
+ config2,
35553
+ async () => await foregroundDataCall(config2, parsed, dependencies, warnings)
35554
+ );
35555
+ }
35556
+ if (parsed.command === "agent-decision-submit") {
35557
+ return await withAgentCommandLock(
35558
+ config2,
35559
+ async () => await foregroundDecisionSubmit(config2, parsed, dependencies, warnings)
35560
+ );
35561
+ }
35562
+ if (parsed.command === "agent-decision-status") {
35563
+ return await withAgentCommandLock(
35564
+ config2,
35565
+ async () => await foregroundDecisionStatus(config2, parsed, dependencies, warnings)
35566
+ );
35567
+ }
35568
+ if (parsed.command === "agent-assignment-release") {
35569
+ return await withAgentCommandLock(
35570
+ config2,
35571
+ async () => await foregroundAssignmentRelease(config2, parsed, dependencies, warnings)
35572
+ );
35573
+ }
35114
35574
  if (parsed.command === "service") {
35115
35575
  const mutationActions = /* @__PURE__ */ new Set(["install", "start", "stop", "recover", "uninstall"]);
35116
35576
  if (!parsed.serviceAction || !mutationActions.has(parsed.serviceAction)) {
@@ -35141,7 +35601,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
35141
35601
  return await cleanupLogin(config2, parsed, dependencies, warnings, true);
35142
35602
  }
35143
35603
  throw new Error(
35144
- "Usage: vtx inference-host <login|codex-login|run|agent-connect|agent-run|agent-next|agent-complete|agent-fail|service|status|doctor|logout|codex-logout|revoke> [--json]"
35604
+ "Usage: vtx inference-host <login|codex-login|run|agent-connect|agent-run|agent-next|agent-complete|agent-fail|agent-assignment-next|agent-assignment-heartbeat|agent-data-call|agent-decision-submit|agent-decision-status|agent-assignment-release|service|status|doctor|logout|codex-logout|revoke> [--json]"
35145
35605
  );
35146
35606
  } catch (error48) {
35147
35607
  return {
@@ -35152,7 +35612,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
35152
35612
  };
35153
35613
  }
35154
35614
  }
35155
- var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, credentialLifecycleLockPath, serviceRecoveryCommandLockPath, AGENT_HEARTBEAT_INTERVAL_MS, AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS, retryableAgentHeartbeatError, assertRevocationCheckpoint, readRevocationCheckpoint, writeRevocationCheckpoint, clearRevocationCheckpoint, render, INFERENCE_HOST_HELP, parseHostConcurrency, parseInferenceHostArgs, resolveDurableServiceDisplayName, defaultOpenBrowser, registerInferenceHostServiceControlInput, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, restoreInferenceHostCredentialContext, recoverInferenceHostCredentialContextTransition, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertDurableServiceUninstalled, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, preparePortableDurableAdapter, defaultRunPortableDurableAdapter, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock, recoveryBackupPath, serviceRecoveryTransactionPath, readServiceRecoveryTransaction, assertResumableCodexRecoveryEvidence, recoverInstalledCodexService, serviceCommand, hasExplicitCredentialStoreConfiguration, credentialEnvironmentForIdentity, commandRequiresRetainedCredentialStore, commandUsesInstalledServiceCredentials, resolveInferenceHostCommandConfig;
35615
+ var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, credentialLifecycleLockPath, serviceRecoveryCommandLockPath, AGENT_HEARTBEAT_INTERVAL_MS, AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS, retryableAgentHeartbeatError, assertRevocationCheckpoint, readRevocationCheckpoint, writeRevocationCheckpoint, clearRevocationCheckpoint, render, INFERENCE_HOST_HELP, parseHostConcurrency, parseInferenceHostArgs, resolveDurableServiceDisplayName, defaultOpenBrowser, registerInferenceHostServiceControlInput, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, restoreInferenceHostCredentialContext, recoverInferenceHostCredentialContextTransition, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertDurableServiceUninstalled, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, preparePortableDurableAdapter, defaultRunPortableDurableAdapter, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, parseOptionalAgentStdin, agentOperationId, agentSession, agentConnect, foregroundAgentControlState, requireForegroundAgentState, assignmentFromClaim, foregroundAssignmentNext, foregroundAssignmentHeartbeat, foregroundDataCall, foregroundDecisionStatusRequest, foregroundAssignmentLeaseEnded, handleForegroundCancelledAssignment, foregroundDecisionStatus, foregroundDecisionSubmit, foregroundAssignmentRelease, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock, recoveryBackupPath, serviceRecoveryTransactionPath, readServiceRecoveryTransaction, assertResumableCodexRecoveryEvidence, recoverInstalledCodexService, serviceCommand, hasExplicitCredentialStoreConfiguration, credentialEnvironmentForIdentity, commandRequiresRetainedCredentialStore, commandUsesInstalledServiceCredentials, resolveInferenceHostCommandConfig;
35156
35616
  var init_cli = __esm({
35157
35617
  "lib/inference-host/cli.ts"() {
35158
35618
  "use strict";
@@ -35252,6 +35712,12 @@ Commands:
35252
35712
  agent-next Claim the next exact VTX inference request
35253
35713
  agent-complete Submit one completed agent result from stdin
35254
35714
  agent-fail Submit one truthful agent failure from stdin
35715
+ agent-assignment-next Claim the assigned Main bot for Agent control
35716
+ agent-assignment-heartbeat Renew the current Agent assignment
35717
+ agent-data-call Request allowed assignment-scoped VTX data
35718
+ agent-decision-submit Submit the normal structured trading decision
35719
+ agent-decision-status Resolve an uncertain Agent decision submission
35720
+ agent-assignment-release Release the current Agent assignment
35255
35721
  service Install and control the durable background host
35256
35722
  status Inspect local host and credential state
35257
35723
  doctor Verify credentials, provider runtime, and private state
@@ -35566,6 +36032,7 @@ Durable service:
35566
36032
  );
35567
36033
  await clearInferenceAgentAttemptState(config2.statePath);
35568
36034
  await clearInferenceAgentNextState(config2.statePath);
36035
+ await clearInferenceForegroundAgentControlState(config2.statePath);
35569
36036
  await clearCodexAgentRuntimeState(config2.statePath);
35570
36037
  await clearInferenceHostLocalState(config2.statePath);
35571
36038
  };
@@ -35593,10 +36060,11 @@ Durable service:
35593
36060
  const revocationCheckpointPresent = await readRevocationCheckpoint(config2) !== null;
35594
36061
  const agentAttemptPresent = await readInferenceAgentAttemptState(config2.statePath) !== null;
35595
36062
  const agentNextRecoveryPresent = await readInferenceAgentNextState(config2.statePath) !== null;
36063
+ const foregroundAgentControlPresent = await readInferenceForegroundAgentControlState(config2.statePath) !== null;
35596
36064
  const codexAgentRuntimePresent = await readCodexAgentRuntimeState(config2.statePath) !== null;
35597
- if (pendingAttempts > 0 || codexRecoveryPresent || serviceRecoveryPresent || revocationCheckpointPresent || agentAttemptPresent || agentNextRecoveryPresent || codexAgentRuntimePresent) {
36065
+ if (pendingAttempts > 0 || codexRecoveryPresent || serviceRecoveryPresent || revocationCheckpointPresent || agentAttemptPresent || agentNextRecoveryPresent || foregroundAgentControlPresent || codexAgentRuntimePresent) {
35598
36066
  throw new Error(
35599
- "Inference host logout refused because attempt recovery is still pending (Codex, agent-driven, or revocation recovery). Rerun agent-next, complete or fail the active attempt, run the automated host to reconcile it, or resume revoke."
36067
+ "Inference host logout refused because attempt recovery is still pending (Codex, Provider, Agent-control, or revocation recovery). Reconcile or release the exact active operation before retrying logout, or resume revoke."
35600
36068
  );
35601
36069
  }
35602
36070
  };
@@ -35669,6 +36137,12 @@ Durable service:
35669
36137
  return { adapter, preflight: await adapter.preflight(signal) };
35670
36138
  };
35671
36139
  defaultRunPortableDurableAdapter = async (options) => {
36140
+ const agentAdapter = options.adapter;
36141
+ if (typeof agentAdapter.runTurn !== "function" || typeof agentAdapter.releaseThread !== "function" || typeof agentAdapter.close !== "function") {
36142
+ throw new Error(
36143
+ `${options.preflight.adapterId} does not implement the durable Agent-control contract.`
36144
+ );
36145
+ }
35672
36146
  const dependencies = createDefaultInferenceHostRunnerDependencies({
35673
36147
  apiUrl: options.config.apiUrl,
35674
36148
  statePath: options.config.statePath,
@@ -35704,6 +36178,10 @@ Durable service:
35704
36178
  maxConcurrency: options.maxConcurrency,
35705
36179
  once: options.once,
35706
36180
  emitDiagnosticEvent: options.emitDiagnosticEvent,
36181
+ agentRuntime: {
36182
+ statePath: options.config.statePath,
36183
+ adapter: agentAdapter
36184
+ },
35707
36185
  signal: options.signal
35708
36186
  }).run();
35709
36187
  };
@@ -35737,7 +36215,7 @@ Durable service:
35737
36215
  await writeInferenceHostCredentialContextTransition(config2, previousCredentialContext);
35738
36216
  await writeInferenceHostCredentialContext(config2);
35739
36217
  const keyPair = generateExternalInferenceEnvelopeKeyPair();
35740
- const hostId = randomUUID3();
36218
+ const hostId = randomUUID4();
35741
36219
  const beginLogin = dependencies.beginLogin ?? beginInferenceOAuthLogin;
35742
36220
  const loginStore = {
35743
36221
  kind: store.kind,
@@ -36417,7 +36895,27 @@ Waiting for approval...
36417
36895
  }
36418
36896
  return record2;
36419
36897
  };
36420
- agentOperationId = (kind) => `${kind}-${randomUUID3()}`;
36898
+ parseOptionalAgentStdin = async (dependencies, allowedKeys) => {
36899
+ if (!dependencies.readStdin && process.stdin.isTTY) return {};
36900
+ const raw = await (dependencies.readStdin ?? defaultReadStdin)();
36901
+ if (!raw.trim()) return {};
36902
+ let value;
36903
+ try {
36904
+ value = JSON.parse(raw);
36905
+ } catch {
36906
+ throw new Error("Agent-driven inference stdin must be one JSON object.");
36907
+ }
36908
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
36909
+ throw new Error("Agent-driven inference stdin must be one JSON object.");
36910
+ }
36911
+ const record2 = value;
36912
+ const unexpected = Object.keys(record2).filter((key) => !allowedKeys.includes(key));
36913
+ if (unexpected.length > 0) {
36914
+ throw new Error(`Unsupported agent-driven inference fields: ${unexpected.join(", ")}.`);
36915
+ }
36916
+ return record2;
36917
+ };
36918
+ agentOperationId = (kind) => `${kind}-${randomUUID4()}`;
36421
36919
  agentSession = async (config2, dependencies, warnings, signal) => {
36422
36920
  if (await readRevocationCheckpoint(config2)) {
36423
36921
  throw new Error("Inference host revocation recovery must finish first.");
@@ -36461,11 +36959,419 @@ Waiting for approval...
36461
36959
  reasoning_effort: result2.reasoning_effort,
36462
36960
  lanes: ["main", "review", "screener"],
36463
36961
  response_modes: ["provider_response", "decision_candidate"],
36962
+ control_modes: ["provider", "agent"],
36464
36963
  execution_modes: ["client", "server"],
36465
36964
  next_command: "vtx inference-host agent-next --wait-seconds 50 --json",
36965
+ agent_next_command: "vtx inference-host agent-assignment-next --wait-seconds 50 --json",
36466
36966
  keeper_command: "vtx inference-host agent-run"
36467
36967
  }, parsed.json),
36468
36968
  stderr: warnings.length > 0 ? `${warnings.join("\n")}
36969
+ ` : ""
36970
+ };
36971
+ };
36972
+ foregroundAgentControlState = (hostId, updatedAt) => ({
36973
+ schema_version: "vtx_foreground_agent_control_v1",
36974
+ host_id: hostId,
36975
+ assignment: null,
36976
+ pending_next: null,
36977
+ pending_decision: null,
36978
+ next_wake_at: null,
36979
+ updated_at: updatedAt
36980
+ });
36981
+ requireForegroundAgentState = async (config2, hostId) => {
36982
+ const state = await readInferenceForegroundAgentControlState(config2.statePath);
36983
+ if (!state?.assignment) {
36984
+ throw new Error("No active foreground Agent assignment. Run agent-assignment-next first.");
36985
+ }
36986
+ if (state.host_id !== hostId) {
36987
+ throw new Error("Foreground Agent assignment belongs to another inference host.");
36988
+ }
36989
+ return state;
36990
+ };
36991
+ assignmentFromClaim = (result2) => ({
36992
+ assignment_id: result2.assignment_id,
36993
+ assignment_generation: result2.assignment_generation,
36994
+ model_id: result2.model_id,
36995
+ reasoning_effort: result2.reasoning_effort,
36996
+ bot_mode: result2.bot_mode,
36997
+ execution_mode: result2.execution_mode,
36998
+ allowed_symbols: result2.allowed_symbols,
36999
+ data_contract: result2.data_contract,
37000
+ output_schema: result2.output_schema,
37001
+ minimum_wake_seconds: result2.minimum_wake_seconds,
37002
+ maximum_wake_seconds: result2.maximum_wake_seconds,
37003
+ lease_expires_at: result2.lease_expires_at
37004
+ });
37005
+ foregroundAssignmentNext = async (config2, parsed, dependencies, warnings) => {
37006
+ const session = await agentSession(config2, dependencies, warnings);
37007
+ const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
37008
+ const sleep4 = dependencies.sleep ?? (async (milliseconds) => {
37009
+ await new Promise((resolve6) => setTimeout(resolve6, milliseconds));
37010
+ });
37011
+ const stopAt = now().getTime() + parsed.waitSeconds * 1e3;
37012
+ let state = await readInferenceForegroundAgentControlState(config2.statePath) ?? foregroundAgentControlState(session.localState.host_id, now().toISOString());
37013
+ if (state.host_id !== session.localState.host_id) {
37014
+ throw new Error("Foreground Agent recovery state belongs to another inference host.");
37015
+ }
37016
+ if (state.pending_decision) {
37017
+ throw new Error("Resolve the pending Agent decision before claiming another assignment.");
37018
+ }
37019
+ while (true) {
37020
+ if (!state.pending_next) {
37021
+ const requestedAt = now().toISOString();
37022
+ state = {
37023
+ ...state,
37024
+ pending_next: {
37025
+ operation_id: agentOperationId("agent-assignment-next"),
37026
+ requested_at: requestedAt
37027
+ },
37028
+ updated_at: requestedAt
37029
+ };
37030
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37031
+ }
37032
+ const pending = state.pending_next;
37033
+ let result2;
37034
+ try {
37035
+ result2 = await session.client.callTool("inference.agent.assignment.next", {
37036
+ operation_id: pending.operation_id,
37037
+ host_id: session.localState.host_id,
37038
+ requested_at: pending.requested_at,
37039
+ contract_version: "agent_assignment_v2",
37040
+ ...state.assignment ? {
37041
+ assignment_id: state.assignment.assignment_id,
37042
+ assignment_generation: state.assignment.assignment_generation
37043
+ } : {}
37044
+ });
37045
+ } catch (error48) {
37046
+ if (error48 && typeof error48 === "object" && error48.definitivelyNotApplied === true) {
37047
+ state = { ...state, pending_next: null, updated_at: now().toISOString() };
37048
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37049
+ }
37050
+ throw error48;
37051
+ }
37052
+ if (result2.claim_state === "claimed") {
37053
+ const assignment = assignmentFromClaim(result2);
37054
+ state = {
37055
+ ...state,
37056
+ assignment,
37057
+ pending_next: null,
37058
+ next_wake_at: null,
37059
+ updated_at: now().toISOString()
37060
+ };
37061
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37062
+ return {
37063
+ exitCode: 0,
37064
+ stdout: render({
37065
+ ...result2,
37066
+ instructions: "VTX supplied no trading prompt or prepared context. Choose your own cadence; use agent-data-call zero or more times, submit the exact output_schema through agent-decision-submit, then record the next wake with agent-assignment-heartbeat. Keep agent-run open for liveness.",
37067
+ data_command: "vtx inference-host agent-data-call --json",
37068
+ decision_command: "vtx inference-host agent-decision-submit --json",
37069
+ decision_input: {
37070
+ candidate: "<object matching output_schema>",
37071
+ provenance: {
37072
+ source: "external_agent",
37073
+ agent_run_id: "<stable harness run id>",
37074
+ requested_model: result2.model_id,
37075
+ effective_model: "<actual model used>",
37076
+ requested_reasoning_effort: result2.reasoning_effort,
37077
+ effective_reasoning_effort: "<actual reasoning effort used>"
37078
+ }
37079
+ },
37080
+ heartbeat_command: "vtx inference-host agent-assignment-heartbeat --json",
37081
+ release_command: "vtx inference-host agent-assignment-release --json"
37082
+ }, parsed.json),
37083
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37084
+ ` : ""
37085
+ };
37086
+ }
37087
+ state = { ...state, pending_next: null, updated_at: now().toISOString() };
37088
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37089
+ const remaining = stopAt - now().getTime();
37090
+ if (remaining <= 0 || parsed.waitSeconds === 0) {
37091
+ return {
37092
+ exitCode: 0,
37093
+ stdout: render(result2, parsed.json),
37094
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37095
+ ` : ""
37096
+ };
37097
+ }
37098
+ await sleep4(Math.min(remaining, Math.max(50, result2.retry_after_ms)));
37099
+ }
37100
+ };
37101
+ foregroundAssignmentHeartbeat = async (config2, parsed, dependencies, warnings) => {
37102
+ const session = await agentSession(config2, dependencies, warnings);
37103
+ const state = await requireForegroundAgentState(
37104
+ config2,
37105
+ session.localState.host_id
37106
+ );
37107
+ const input = await parseOptionalAgentStdin(dependencies, ["next_wake_at"]);
37108
+ const nextWakeAt = input.next_wake_at;
37109
+ if (nextWakeAt !== void 0 && (typeof nextWakeAt !== "string" || !Number.isFinite(Date.parse(nextWakeAt)))) {
37110
+ throw new Error("next_wake_at must be an ISO timestamp.");
37111
+ }
37112
+ const requestedAt = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
37113
+ const result2 = await session.client.callTool("inference.agent.assignment.heartbeat", {
37114
+ operation_id: agentOperationId("agent-assignment-heartbeat"),
37115
+ host_id: session.localState.host_id,
37116
+ assignment_id: state.assignment.assignment_id,
37117
+ assignment_generation: state.assignment.assignment_generation,
37118
+ requested_at: requestedAt,
37119
+ ...nextWakeAt === void 0 ? {} : { next_wake_at: nextWakeAt }
37120
+ });
37121
+ if (result2.directive === "cancel") {
37122
+ await handleForegroundCancelledAssignment(config2, session, state, requestedAt);
37123
+ } else {
37124
+ await writeInferenceForegroundAgentControlState(config2.statePath, {
37125
+ ...state,
37126
+ assignment: { ...state.assignment, lease_expires_at: result2.lease_expires_at },
37127
+ next_wake_at: result2.next_wake_at ?? state.next_wake_at,
37128
+ updated_at: requestedAt
37129
+ });
37130
+ }
37131
+ return {
37132
+ exitCode: 0,
37133
+ stdout: render(result2, parsed.json),
37134
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37135
+ ` : ""
37136
+ };
37137
+ };
37138
+ foregroundDataCall = async (config2, parsed, dependencies, warnings) => {
37139
+ const session = await agentSession(config2, dependencies, warnings);
37140
+ const state = await requireForegroundAgentState(config2, session.localState.host_id);
37141
+ const input = await parseAgentStdin(dependencies, ["capability", "arguments"]);
37142
+ const capability = typeof input.capability === "string" ? input.capability.trim() : "";
37143
+ if (!capability || !state.assignment.data_contract.some((item) => item.id === capability)) {
37144
+ throw new Error("Agent data capability is not allowed by the current assignment.");
37145
+ }
37146
+ if (!input.arguments || typeof input.arguments !== "object" || Array.isArray(input.arguments)) {
37147
+ throw new Error("Agent data arguments must be one JSON object.");
37148
+ }
37149
+ const result2 = await session.client.callTool("inference.agent.data.call", {
37150
+ operation_id: agentOperationId("agent-data-call"),
37151
+ host_id: session.localState.host_id,
37152
+ assignment_id: state.assignment.assignment_id,
37153
+ assignment_generation: state.assignment.assignment_generation,
37154
+ capability,
37155
+ arguments: input.arguments,
37156
+ requested_at: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
37157
+ });
37158
+ return {
37159
+ exitCode: 0,
37160
+ stdout: render(result2, parsed.json),
37161
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37162
+ ` : ""
37163
+ };
37164
+ };
37165
+ foregroundDecisionStatusRequest = (hostId, state) => ({
37166
+ operation_id: state.pending_decision.request.operation_id,
37167
+ host_id: hostId,
37168
+ assignment_id: state.assignment.assignment_id,
37169
+ assignment_generation: state.assignment.assignment_generation
37170
+ });
37171
+ foregroundAssignmentLeaseEnded = (state, now) => Boolean(state.assignment) && Date.parse(state.assignment.lease_expires_at) <= now.getTime();
37172
+ handleForegroundCancelledAssignment = async (config2, session, state, requestedAt, signal) => {
37173
+ if (!state.pending_decision) {
37174
+ await clearInferenceForegroundAgentControlState(config2.statePath);
37175
+ return;
37176
+ }
37177
+ const cancelledState = {
37178
+ ...state,
37179
+ assignment: {
37180
+ ...state.assignment,
37181
+ lease_expires_at: requestedAt
37182
+ },
37183
+ pending_decision: {
37184
+ ...state.pending_decision,
37185
+ last_status_check_at: requestedAt
37186
+ },
37187
+ next_wake_at: null,
37188
+ updated_at: requestedAt
37189
+ };
37190
+ await writeInferenceForegroundAgentControlState(config2.statePath, cancelledState);
37191
+ try {
37192
+ await session.client.callTool(
37193
+ "inference.agent.decision.status",
37194
+ foregroundDecisionStatusRequest(session.localState.host_id, cancelledState),
37195
+ { signal }
37196
+ );
37197
+ } catch {
37198
+ return;
37199
+ }
37200
+ await clearInferenceForegroundAgentControlState(config2.statePath);
37201
+ };
37202
+ foregroundDecisionStatus = async (config2, parsed, dependencies, warnings) => {
37203
+ const session = await agentSession(config2, dependencies, warnings);
37204
+ let state = await requireForegroundAgentState(config2, session.localState.host_id);
37205
+ if (!state.pending_decision) {
37206
+ throw new Error("No uncertain foreground Agent decision requires status recovery.");
37207
+ }
37208
+ const checkedAt = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
37209
+ state = {
37210
+ ...state,
37211
+ pending_decision: { ...state.pending_decision, last_status_check_at: checkedAt },
37212
+ updated_at: checkedAt
37213
+ };
37214
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37215
+ const result2 = await session.client.callTool(
37216
+ "inference.agent.decision.status",
37217
+ foregroundDecisionStatusRequest(session.localState.host_id, state)
37218
+ );
37219
+ const assignmentLeaseEnded = foregroundAssignmentLeaseEnded(state, new Date(checkedAt));
37220
+ if (result2.found || assignmentLeaseEnded) {
37221
+ if (assignmentLeaseEnded) {
37222
+ await clearInferenceForegroundAgentControlState(config2.statePath);
37223
+ } else {
37224
+ await writeInferenceForegroundAgentControlState(config2.statePath, {
37225
+ ...state,
37226
+ pending_decision: null,
37227
+ updated_at: checkedAt
37228
+ });
37229
+ }
37230
+ }
37231
+ return {
37232
+ exitCode: 0,
37233
+ stdout: render(result2, parsed.json),
37234
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37235
+ ` : ""
37236
+ };
37237
+ };
37238
+ foregroundDecisionSubmit = async (config2, parsed, dependencies, warnings) => {
37239
+ const session = await agentSession(config2, dependencies, warnings);
37240
+ const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
37241
+ let state = await requireForegroundAgentState(config2, session.localState.host_id);
37242
+ if (!state.pending_decision) {
37243
+ const input = await parseAgentStdin(dependencies, ["candidate", "provenance"]);
37244
+ if (!input.candidate || typeof input.candidate !== "object" || Array.isArray(input.candidate)) {
37245
+ throw new Error("Agent decision candidate must be one JSON object.");
37246
+ }
37247
+ if (!input.provenance || typeof input.provenance !== "object" || Array.isArray(input.provenance)) {
37248
+ throw new Error("Agent decision provenance must be one JSON object.");
37249
+ }
37250
+ const observedAt = now().toISOString();
37251
+ state = {
37252
+ ...state,
37253
+ pending_decision: {
37254
+ request: {
37255
+ assignment_id: state.assignment.assignment_id,
37256
+ assignment_generation: state.assignment.assignment_generation,
37257
+ operation_id: agentOperationId("agent-decision-submit"),
37258
+ candidate: input.candidate,
37259
+ observed_at: observedAt,
37260
+ provenance: input.provenance
37261
+ },
37262
+ first_transmit_at: null,
37263
+ last_status_check_at: null
37264
+ },
37265
+ updated_at: observedAt
37266
+ };
37267
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37268
+ } else {
37269
+ const checkedAt = now().toISOString();
37270
+ state = {
37271
+ ...state,
37272
+ pending_decision: { ...state.pending_decision, last_status_check_at: checkedAt },
37273
+ updated_at: checkedAt
37274
+ };
37275
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37276
+ const status = await session.client.callTool(
37277
+ "inference.agent.decision.status",
37278
+ foregroundDecisionStatusRequest(session.localState.host_id, state)
37279
+ );
37280
+ if (status.found) {
37281
+ await writeInferenceForegroundAgentControlState(config2.statePath, {
37282
+ ...state,
37283
+ pending_decision: null,
37284
+ updated_at: checkedAt
37285
+ });
37286
+ return {
37287
+ exitCode: 0,
37288
+ stdout: render({ ...status, recovered: true }, parsed.json),
37289
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37290
+ ` : ""
37291
+ };
37292
+ }
37293
+ }
37294
+ const transmittedAt = now().toISOString();
37295
+ state = {
37296
+ ...state,
37297
+ pending_decision: {
37298
+ ...state.pending_decision,
37299
+ first_transmit_at: state.pending_decision.first_transmit_at ?? transmittedAt
37300
+ },
37301
+ updated_at: transmittedAt
37302
+ };
37303
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37304
+ try {
37305
+ const result2 = await session.client.callTool(
37306
+ "inference.agent.decision.submit",
37307
+ state.pending_decision.request
37308
+ );
37309
+ await writeInferenceForegroundAgentControlState(config2.statePath, {
37310
+ ...state,
37311
+ pending_decision: null,
37312
+ updated_at: now().toISOString()
37313
+ });
37314
+ return {
37315
+ exitCode: 0,
37316
+ stdout: render(result2, parsed.json),
37317
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37318
+ ` : ""
37319
+ };
37320
+ } catch (error48) {
37321
+ const checkedAt = now().toISOString();
37322
+ state = {
37323
+ ...state,
37324
+ pending_decision: { ...state.pending_decision, last_status_check_at: checkedAt },
37325
+ updated_at: checkedAt
37326
+ };
37327
+ await writeInferenceForegroundAgentControlState(config2.statePath, state);
37328
+ try {
37329
+ const status = await session.client.callTool(
37330
+ "inference.agent.decision.status",
37331
+ foregroundDecisionStatusRequest(session.localState.host_id, state)
37332
+ );
37333
+ if (status.found) {
37334
+ await writeInferenceForegroundAgentControlState(config2.statePath, {
37335
+ ...state,
37336
+ pending_decision: null,
37337
+ updated_at: checkedAt
37338
+ });
37339
+ return {
37340
+ exitCode: 0,
37341
+ stdout: render({ ...status, recovered: true }, parsed.json),
37342
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
37343
+ ` : ""
37344
+ };
37345
+ }
37346
+ } catch {
37347
+ }
37348
+ throw error48;
37349
+ }
37350
+ };
37351
+ foregroundAssignmentRelease = async (config2, parsed, dependencies, warnings) => {
37352
+ const session = await agentSession(config2, dependencies, warnings);
37353
+ const state = await requireForegroundAgentState(config2, session.localState.host_id);
37354
+ if (state.pending_decision) {
37355
+ throw new Error("Resolve the pending Agent decision before releasing its assignment.");
37356
+ }
37357
+ const input = await parseOptionalAgentStdin(dependencies, ["reason_code"]);
37358
+ const reasonCode = input.reason_code === void 0 ? "agent_released" : input.reason_code;
37359
+ if (typeof reasonCode !== "string" || !/^[a-z0-9][a-z0-9._-]{0,95}$/u.test(reasonCode)) {
37360
+ throw new Error("reason_code must be a safe lowercase code.");
37361
+ }
37362
+ const result2 = await session.client.callTool("inference.agent.assignment.release", {
37363
+ operation_id: agentOperationId("agent-assignment-release"),
37364
+ host_id: session.localState.host_id,
37365
+ assignment_id: state.assignment.assignment_id,
37366
+ assignment_generation: state.assignment.assignment_generation,
37367
+ reason_code: reasonCode,
37368
+ requested_at: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
37369
+ });
37370
+ await clearInferenceForegroundAgentControlState(config2.statePath);
37371
+ return {
37372
+ exitCode: 0,
37373
+ stdout: render(result2, parsed.json),
37374
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
36469
37375
  ` : ""
36470
37376
  };
36471
37377
  };
@@ -36473,6 +37379,7 @@ Waiting for approval...
36473
37379
  const cancellation = lifecycleCancellation(dependencies);
36474
37380
  let keeperLock = null;
36475
37381
  let heartbeatCount = 0;
37382
+ let assignmentHeartbeatCount = 0;
36476
37383
  try {
36477
37384
  keeperLock = await acquireInferenceHostProcessLock(foregroundHostLockPath(config2));
36478
37385
  const store = configuredCredentialStore(config2, dependencies, (message) => {
@@ -36506,6 +37413,7 @@ Waiting for approval...
36506
37413
  }
36507
37414
  if (commandLock) {
36508
37415
  let heartbeatSuppressed = false;
37416
+ let assignmentHeartbeatComplete = true;
36509
37417
  try {
36510
37418
  const active = await readInferenceAgentAttemptState(config2.statePath);
36511
37419
  const pendingNext = await readInferenceAgentNextState(config2.statePath);
@@ -36565,10 +37473,65 @@ Waiting for approval...
36565
37473
  }
36566
37474
  }
36567
37475
  }
37476
+ const control = await readInferenceForegroundAgentControlState(config2.statePath);
37477
+ if (control && control.host_id !== session.localState.host_id) {
37478
+ throw new Error("Foreground Agent control state belongs to another inference host.");
37479
+ }
37480
+ if (control?.assignment) {
37481
+ assignmentHeartbeatComplete = false;
37482
+ try {
37483
+ const requestedAt = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
37484
+ const result2 = await session.client.callTool(
37485
+ "inference.agent.assignment.heartbeat",
37486
+ {
37487
+ operation_id: agentOperationId("agent-assignment-heartbeat"),
37488
+ host_id: session.localState.host_id,
37489
+ assignment_id: control.assignment.assignment_id,
37490
+ assignment_generation: control.assignment.assignment_generation,
37491
+ requested_at: requestedAt
37492
+ },
37493
+ { signal: cancellation.signal }
37494
+ );
37495
+ assignmentHeartbeatComplete = true;
37496
+ assignmentHeartbeatCount += 1;
37497
+ if (result2.directive === "cancel") {
37498
+ await handleForegroundCancelledAssignment(
37499
+ config2,
37500
+ session,
37501
+ control,
37502
+ requestedAt,
37503
+ cancellation.signal
37504
+ );
37505
+ } else {
37506
+ await writeInferenceForegroundAgentControlState(config2.statePath, {
37507
+ ...control,
37508
+ assignment: {
37509
+ ...control.assignment,
37510
+ lease_expires_at: result2.lease_expires_at
37511
+ },
37512
+ updated_at: requestedAt
37513
+ });
37514
+ }
37515
+ } catch (error48) {
37516
+ if (cancellation.signal.aborted) break;
37517
+ if (!retryableAgentHeartbeatError(error48)) throw error48;
37518
+ consecutiveHeartbeatFailures += 1;
37519
+ retryDelayMs = Math.min(
37520
+ AGENT_HEARTBEAT_INTERVAL_MS * 2 ** Math.max(0, consecutiveHeartbeatFailures - 1),
37521
+ AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS
37522
+ );
37523
+ emitStderr(parsed.json ? `${JSON.stringify({
37524
+ status: "assignment_heartbeat_retry",
37525
+ retry_after_ms: retryDelayMs
37526
+ })}
37527
+ ` : `Foreground Agent assignment heartbeat was temporarily unavailable; retrying in ${retryDelayMs}ms.
37528
+ `);
37529
+ }
37530
+ }
36568
37531
  } finally {
36569
37532
  await commandLock.release();
36570
37533
  }
36571
- if (parsed.once && (heartbeatCount > 0 || heartbeatSuppressed)) break;
37534
+ if (parsed.once && (heartbeatCount > 0 || heartbeatSuppressed) && assignmentHeartbeatComplete) break;
36572
37535
  }
36573
37536
  if (parsed.once && !commandLock) break;
36574
37537
  await (dependencies.sleep ?? (async (milliseconds) => {
@@ -36577,7 +37540,11 @@ Waiting for approval...
36577
37540
  }
36578
37541
  return {
36579
37542
  exitCode: 0,
36580
- stdout: render({ status: "stopped", heartbeats: heartbeatCount }, parsed.json),
37543
+ stdout: render({
37544
+ status: "stopped",
37545
+ heartbeats: heartbeatCount,
37546
+ assignment_heartbeats: assignmentHeartbeatCount
37547
+ }, parsed.json),
36581
37548
  stderr: warnings.length > 0 ? `${warnings.join("\n")}
36582
37549
  ` : ""
36583
37550
  };
@@ -36965,7 +37932,7 @@ Waiting for approval...
36965
37932
  if (recoveryRaw === null && existingTransaction?.phase !== "reconciled") {
36966
37933
  throw new Error("Codex recovery evidence changed before service recovery began.");
36967
37934
  }
36968
- const transactionId = existingTransaction?.transaction_id ?? randomUUID3();
37935
+ const transactionId = existingTransaction?.transaction_id ?? randomUUID4();
36969
37936
  const backupPath = existingTransaction?.recovery_backup_path ?? recoveryBackupPath(config2, transactionId);
36970
37937
  if (!existingTransaction) {
36971
37938
  await writeAtomicInferencePrivateFile(backupPath, recoveryRaw);
@@ -37486,7 +38453,7 @@ var init_types = __esm({
37486
38453
  });
37487
38454
 
37488
38455
  // lib/agent-core/client.ts
37489
- import { randomUUID as randomUUID4 } from "node:crypto";
38456
+ import { randomUUID as randomUUID5 } from "node:crypto";
37490
38457
  function normalizeApiUrl(value) {
37491
38458
  const parsed = String(value || "").trim();
37492
38459
  if (!parsed) {
@@ -37748,7 +38715,7 @@ var init_client = __esm({
37748
38715
  return this.request("/trading/ai/runtime/decision", {
37749
38716
  method: "POST",
37750
38717
  profileId,
37751
- idempotencyKey: randomUUID4(),
38718
+ idempotencyKey: randomUUID5(),
37752
38719
  headers: { "x-client-runtime-lease": leaseToken },
37753
38720
  body: payload
37754
38721
  });
@@ -37757,7 +38724,7 @@ var init_client = __esm({
37757
38724
  return this.request("/trading/ai/runtime/trade-sync", {
37758
38725
  method: "POST",
37759
38726
  profileId,
37760
- idempotencyKey: randomUUID4(),
38727
+ idempotencyKey: randomUUID5(),
37761
38728
  headers: { "x-client-runtime-lease": leaseToken },
37762
38729
  body: payload
37763
38730
  });
@@ -37766,7 +38733,7 @@ var init_client = __esm({
37766
38733
  return this.request("/trading/ai/runtime/error", {
37767
38734
  method: "POST",
37768
38735
  profileId,
37769
- idempotencyKey: randomUUID4(),
38736
+ idempotencyKey: randomUUID5(),
37770
38737
  headers: { "x-client-runtime-lease": leaseToken },
37771
38738
  body: payload
37772
38739
  });
@@ -37778,7 +38745,7 @@ var init_client = __esm({
37778
38745
  return this.request("/trading/market-order", {
37779
38746
  method: "POST",
37780
38747
  profileId,
37781
- idempotencyKey: randomUUID4(),
38748
+ idempotencyKey: randomUUID5(),
37782
38749
  body: payload
37783
38750
  });
37784
38751
  }
@@ -37786,7 +38753,7 @@ var init_client = __esm({
37786
38753
  return this.request("/trading/limit-order", {
37787
38754
  method: "POST",
37788
38755
  profileId,
37789
- idempotencyKey: randomUUID4(),
38756
+ idempotencyKey: randomUUID5(),
37790
38757
  body: payload
37791
38758
  });
37792
38759
  }
@@ -37794,7 +38761,7 @@ var init_client = __esm({
37794
38761
  return this.request("/trading/cancel-order", {
37795
38762
  method: "POST",
37796
38763
  profileId,
37797
- idempotencyKey: randomUUID4(),
38764
+ idempotencyKey: randomUUID5(),
37798
38765
  body: payload
37799
38766
  });
37800
38767
  }
@@ -37813,7 +38780,7 @@ var init_client = __esm({
37813
38780
  return this.request("/trading/ai/start", {
37814
38781
  method: "POST",
37815
38782
  profileId,
37816
- idempotencyKey: randomUUID4(),
38783
+ idempotencyKey: randomUUID5(),
37817
38784
  body: payload
37818
38785
  });
37819
38786
  }
@@ -37821,7 +38788,7 @@ var init_client = __esm({
37821
38788
  return this.request("/trading/ai/stop", {
37822
38789
  method: "POST",
37823
38790
  profileId,
37824
- idempotencyKey: randomUUID4(),
38791
+ idempotencyKey: randomUUID5(),
37825
38792
  body: {}
37826
38793
  });
37827
38794
  }
@@ -37829,7 +38796,7 @@ var init_client = __esm({
37829
38796
  return this.request("/trading/ai/assistant/start", {
37830
38797
  method: "POST",
37831
38798
  profileId,
37832
- idempotencyKey: randomUUID4(),
38799
+ idempotencyKey: randomUUID5(),
37833
38800
  body: {}
37834
38801
  });
37835
38802
  }
@@ -37837,7 +38804,7 @@ var init_client = __esm({
37837
38804
  return this.request("/trading/ai/assistant/stop", {
37838
38805
  method: "POST",
37839
38806
  profileId,
37840
- idempotencyKey: randomUUID4(),
38807
+ idempotencyKey: randomUUID5(),
37841
38808
  body: {}
37842
38809
  });
37843
38810
  }
@@ -37845,7 +38812,7 @@ var init_client = __esm({
37845
38812
  return this.request("/trading/ai/runtime/session/start", {
37846
38813
  method: "POST",
37847
38814
  profileId,
37848
- idempotencyKey: randomUUID4(),
38815
+ idempotencyKey: randomUUID5(),
37849
38816
  body: payload
37850
38817
  });
37851
38818
  }
@@ -37856,7 +38823,7 @@ var init_client = __esm({
37856
38823
  return this.request("/trading/ai/runtime/session/stop", {
37857
38824
  method: "POST",
37858
38825
  profileId,
37859
- idempotencyKey: randomUUID4(),
38826
+ idempotencyKey: randomUUID5(),
37860
38827
  body: payload
37861
38828
  });
37862
38829
  }
@@ -37873,7 +38840,7 @@ var init_client = __esm({
37873
38840
  }).request("/trading/ai/runtime/session/stop", {
37874
38841
  method: "POST",
37875
38842
  profileId,
37876
- idempotencyKey: randomUUID4(),
38843
+ idempotencyKey: randomUUID5(),
37877
38844
  body: payload
37878
38845
  });
37879
38846
  }
@@ -37890,7 +38857,7 @@ import {
37890
38857
  unlinkSync,
37891
38858
  writeFileSync
37892
38859
  } from "node:fs";
37893
- import { randomUUID as randomUUID5 } from "node:crypto";
38860
+ import { randomUUID as randomUUID6 } from "node:crypto";
37894
38861
  import { dirname as dirname6 } from "node:path";
37895
38862
  var parseStoredValues, FileBackedProtectionStorage, clientRuntimeProtectionStatePath;
37896
38863
  var init_protection_storage = __esm({
@@ -37947,7 +38914,7 @@ var init_protection_storage = __esm({
37947
38914
  mkdirSync(dirname6(this.path), { recursive: true, mode: 448 });
37948
38915
  const serialized = `${JSON.stringify(values)}
37949
38916
  `;
37950
- const temporaryPath = `${this.path}.${process.pid}.${randomUUID5()}.tmp`;
38917
+ const temporaryPath = `${this.path}.${process.pid}.${randomUUID6()}.tmp`;
37951
38918
  try {
37952
38919
  writeFileSync(temporaryPath, serialized, {
37953
38920
  encoding: "utf8",
@@ -37974,7 +38941,7 @@ var init_protection_storage = __esm({
37974
38941
  });
37975
38942
 
37976
38943
  // lib/agent-core/headless-runtime.ts
37977
- import { randomUUID as randomUUID6 } from "node:crypto";
38944
+ import { randomUUID as randomUUID7 } from "node:crypto";
37978
38945
  import { setTimeout as sleep } from "node:timers/promises";
37979
38946
  function objectOrNull2(value) {
37980
38947
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -38072,8 +39039,8 @@ async function runAndReportLocalWorkCycle(options, state, leaseToken, context) {
38072
39039
  return Boolean(result2.decision || tradeSync || result2.afterDecision);
38073
39040
  }
38074
39041
  async function startHeadlessRuntime(options) {
38075
- const runtimeSessionId = randomUUID6();
38076
- const deviceId = String(options.deviceId || "").trim() || randomUUID6();
39042
+ const runtimeSessionId = randomUUID7();
39043
+ const deviceId = String(options.deviceId || "").trim() || randomUUID7();
38077
39044
  const startResponse = await options.client.startRuntime(options.profileId, {
38078
39045
  session_id: runtimeSessionId,
38079
39046
  device_id: deviceId,
@@ -60625,7 +61592,7 @@ var headless_local_worker_exports = {};
60625
61592
  __export(headless_local_worker_exports, {
60626
61593
  createHeadlessLocalWorker: () => createHeadlessLocalWorker
60627
61594
  });
60628
- import { randomUUID as randomUUID7 } from "node:crypto";
61595
+ import { randomUUID as randomUUID8 } from "node:crypto";
60629
61596
  function objectOrNull3(value) {
60630
61597
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
60631
61598
  }
@@ -61042,7 +62009,7 @@ function createHeadlessLocalWorker(options) {
61042
62009
  const statusMatch = errorText.match(/\b([45]\d{2})\b/);
61043
62010
  const statusCode = statusMatch ? Number(statusMatch[1]) : null;
61044
62011
  const failedInvocation = normalizeAiInvocationTelemetry({
61045
- client_invocation_id: randomUUID7(),
62012
+ client_invocation_id: randomUUID8(),
61046
62013
  use_case: "trader",
61047
62014
  role: "primary",
61048
62015
  attempt_index: 0,
@@ -61098,7 +62065,7 @@ function createHeadlessLocalWorker(options) {
61098
62065
  billable_cached_input_tokens: normalizedUsage.cached_input_tokens
61099
62066
  };
61100
62067
  const invocation = normalizeAiInvocationTelemetry({
61101
- client_invocation_id: randomUUID7(),
62068
+ client_invocation_id: randomUUID8(),
61102
62069
  use_case: "trader",
61103
62070
  role: "primary",
61104
62071
  attempt_index: 0,
@@ -61317,7 +62284,7 @@ var vtx_exports = {};
61317
62284
  __export(vtx_exports, {
61318
62285
  runVtxCli: () => runVtxCli
61319
62286
  });
61320
- import { randomUUID as randomUUID8 } from "node:crypto";
62287
+ import { randomUUID as randomUUID9 } from "node:crypto";
61321
62288
  import { spawn as spawn8 } from "node:child_process";
61322
62289
  function render2(value, json2) {
61323
62290
  if (json2) {
@@ -61731,8 +62698,8 @@ async function runVtxCli(argv2, env = process.env) {
61731
62698
  });
61732
62699
  return { exitCode: 0, stdout: render2(redactCliOutput(response2), json2), stderr: "" };
61733
62700
  }
61734
- const runtimeSessionId = randomUUID8();
61735
- const deviceId = config2.runtimeDeviceId ?? randomUUID8();
62701
+ const runtimeSessionId = randomUUID9();
62702
+ const deviceId = config2.runtimeDeviceId ?? randomUUID9();
61736
62703
  const response = await client.startRuntime(profileId, {
61737
62704
  session_id: runtimeSessionId,
61738
62705
  device_id: deviceId,