@artemiskit/core 0.6.0 → 0.6.1

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 (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +12 -0
  3. package/dist/adapters/types.d.ts +7 -0
  4. package/dist/adapters/types.d.ts.map +1 -1
  5. package/dist/agent-workflow/catalog.d.ts +2 -1
  6. package/dist/agent-workflow/catalog.d.ts.map +1 -1
  7. package/dist/agent-workflow/environment.d.ts +47 -0
  8. package/dist/agent-workflow/environment.d.ts.map +1 -0
  9. package/dist/agent-workflow/index.d.ts +4 -1
  10. package/dist/agent-workflow/index.d.ts.map +1 -1
  11. package/dist/agent-workflow/parser.d.ts +1 -1
  12. package/dist/agent-workflow/parser.d.ts.map +1 -1
  13. package/dist/agent-workflow/sandbox-fixtures/qualify.d.ts +2 -0
  14. package/dist/agent-workflow/sandbox-fixtures/qualify.d.ts.map +1 -0
  15. package/dist/agent-workflow/sandbox.d.ts +12 -0
  16. package/dist/agent-workflow/sandbox.d.ts.map +1 -0
  17. package/dist/agent-workflow/schema.d.ts +117 -7
  18. package/dist/agent-workflow/schema.d.ts.map +1 -1
  19. package/dist/agent-workflow/session.d.ts +113 -0
  20. package/dist/agent-workflow/session.d.ts.map +1 -0
  21. package/dist/agent-workflow/target.d.ts +20 -7
  22. package/dist/agent-workflow/target.d.ts.map +1 -1
  23. package/dist/index.js +1366 -21
  24. package/package.json +1 -1
  25. package/src/adapters/types.ts +7 -0
  26. package/src/agent-workflow/catalog.ts +4 -3
  27. package/src/agent-workflow/environment.ts +207 -0
  28. package/src/agent-workflow/index.ts +5 -1
  29. package/src/agent-workflow/parser.ts +1 -1
  30. package/src/agent-workflow/sandbox-fixtures/qualify.ts +305 -0
  31. package/src/agent-workflow/sandbox.test.ts +117 -0
  32. package/src/agent-workflow/sandbox.ts +438 -0
  33. package/src/agent-workflow/schema.ts +18 -2
  34. package/src/agent-workflow/session.test.ts +629 -0
  35. package/src/agent-workflow/session.ts +1119 -0
  36. package/src/agent-workflow/target.test.ts +5 -1
  37. package/src/agent-workflow/target.ts +82 -17
package/dist/index.js CHANGED
@@ -33425,8 +33425,8 @@ var tools = [
33425
33425
  descriptor("search", "retrieval", "Search declared documents by case-insensitive literal text.", "documents", "read", object({ query: text }), object({ matches: { type: "array", maxItems: 100, items: object({ id: identifier }) } })),
33426
33426
  descriptor("read_document", "documents", "Read one document from simulated state.", "documents", "read", object({ id: identifier }), object({ id: identifier, content: { type: "string", maxLength: 16384 } })),
33427
33427
  descriptor("query_records", "records", "Read a bounded declared collection of structured records.", "records", "read", object({ collection: identifier }), object({ records: { type: "array", maxItems: 100, items: { type: "object" } } })),
33428
- descriptor("read_file", "files", "Read one relative file from in-memory fixture state.", "files", "read", object({ path: relativePath }), object({ content: { type: "string", maxLength: 16384 } })),
33429
- descriptor("write_file", "files", "Write one relative file in isolated in-memory state.", "files", "write", object({ path: relativePath, content: { type: "string", maxLength: 16384 } }), object({ path: relativePath, written: { const: true } })),
33428
+ descriptor("read_file", "files", "Read one relative file from the isolated workflow environment.", "files", "read", object({ path: relativePath }), object({ content: { type: "string", maxLength: 16384 } })),
33429
+ descriptor("write_file", "files", "Write one relative file in the isolated workflow environment.", "files", "write", object({ path: relativePath, content: { type: "string", maxLength: 16384 } }), object({ path: relativePath, written: { const: true } })),
33430
33430
  descriptor("calculator", "computation", "Perform one finite arithmetic operation without evaluating code.", "computation", "none", object({
33431
33431
  operation: { enum: ["add", "subtract", "multiply", "divide"] },
33432
33432
  a: { type: "number" },
@@ -33473,10 +33473,12 @@ function isWorkflowJson(value) {
33473
33473
  if (Array.isArray(item) && (item.length > 1e4 || Object.keys(item).length !== item.length || Object.keys(item).some((key, index) => key !== String(index))))
33474
33474
  return false;
33475
33475
  ancestors.add(item);
33476
- for (const key of Object.keys(item)) {
33476
+ for (const key of Object.getOwnPropertyNames(item)) {
33477
+ if (Array.isArray(item) && key === "length")
33478
+ continue;
33477
33479
  textBytes += Buffer.byteLength(key);
33478
33480
  const entry = Object.getOwnPropertyDescriptor(item, key);
33479
- if (textBytes > 1048576 || forbiddenKeys.has(key) || !entry || !("value" in entry) || !visit(entry.value, depth + 1))
33481
+ if (textBytes > 1048576 || forbiddenKeys.has(key) || !entry || !("value" in entry) || !entry.enumerable || !visit(entry.value, depth + 1))
33480
33482
  return false;
33481
33483
  }
33482
33484
  ancestors.delete(item);
@@ -33503,8 +33505,10 @@ var WorkflowPolicySchema = exports_external.object({
33503
33505
  communication: permission.optional(),
33504
33506
  coordination: permission.optional()
33505
33507
  }).strict(),
33508
+ paths: exports_external.object({ read: exports_external.array(relativePath2).max(1000), write: exports_external.array(relativePath2).max(1000) }).strict().optional(),
33506
33509
  budgets: exports_external.object({
33507
33510
  max_actions: exports_external.number().int().min(1).max(1000),
33511
+ max_model_requests: exports_external.number().int().min(1).max(1000).optional(),
33508
33512
  max_tool_calls: exports_external.number().int().min(1).max(1000).optional(),
33509
33513
  timeout_ms: exports_external.number().int().min(1).max(3600000),
33510
33514
  max_tokens: exports_external.number().int().min(1).max(1e6).optional()
@@ -33543,9 +33547,13 @@ var definition = exports_external.object({
33543
33547
  description: exports_external.string().max(4096).optional(),
33544
33548
  target: exports_external.object({
33545
33549
  provider: exports_external.string().min(1).max(64).regex(/^[a-z0-9_-]+$/),
33546
- model: exports_external.string().min(1).max(256)
33550
+ model: exports_external.string().min(1).max(256),
33551
+ generation: exports_external.object({
33552
+ max_tokens: exports_external.number().int().min(1).max(1e6).optional(),
33553
+ temperature: exports_external.number().min(0).max(2).optional()
33554
+ }).strict().optional()
33547
33555
  }).strict(),
33548
- environment: exports_external.object({ type: exports_external.literal("simulated"), policy: WorkflowPolicySchema }).strict(),
33556
+ environment: exports_external.object({ type: exports_external.enum(["simulated", "sandbox"]), policy: WorkflowPolicySchema }).strict(),
33549
33557
  tools: exports_external.array(exports_external.enum(WORKFLOW_TOOL_IDS)).min(1).max(WORKFLOW_TOOL_IDS.length),
33550
33558
  workflow: exports_external.object({
33551
33559
  system_instructions: exports_external.string().min(1).max(32768),
@@ -33808,6 +33816,7 @@ function executeSimulatedTool(request) {
33808
33816
  }
33809
33817
  // src/agent-workflow/target.ts
33810
33818
  var import_ajv4 = __toESM(require_ajv(), 1);
33819
+ import { createHash as createHash3 } from "node:crypto";
33811
33820
  var identifier2 = exports_external.string().min(1).max(256);
33812
33821
  var toolName = exports_external.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/);
33813
33822
  var text2 = exports_external.string().max(1e6);
@@ -33823,7 +33832,7 @@ var messageSchema = exports_external.object({
33823
33832
  tool_calls: exports_external.array(toolCallSchema).min(1).max(100).optional()
33824
33833
  }).strict();
33825
33834
  var timeoutSchema = exports_external.number().int().min(1).max(2147483647);
33826
- var requestSchema = exports_external.object({
33835
+ var agentTurnRequestSchema = exports_external.object({
33827
33836
  messages: exports_external.array(messageSchema).min(1).max(1000),
33828
33837
  tools: exports_external.array(exports_external.object({
33829
33838
  type: exports_external.literal("function"),
@@ -33847,6 +33856,7 @@ var requestSchema = exports_external.object({
33847
33856
  }).strict()
33848
33857
  }).strict();
33849
33858
  var resultSchema = exports_external.object({
33859
+ usageAvailable: exports_external.boolean().optional(),
33850
33860
  id: identifier2,
33851
33861
  model: identifier2,
33852
33862
  text: text2,
@@ -33880,7 +33890,7 @@ function bounded(run, timeoutMs, signal) {
33880
33890
  Promise.resolve().then(() => signal?.aborted ? failure("error", "aborted") : run()).then(finish, () => finish(failure("error", "target_error")));
33881
33891
  });
33882
33892
  }
33883
- function validTranscript(messages) {
33893
+ function validWorkflowTranscript(messages) {
33884
33894
  const seen = new Set;
33885
33895
  const pending = new Set;
33886
33896
  for (const message of messages) {
@@ -33911,14 +33921,30 @@ function createModelClientTarget(client) {
33911
33921
  if (!client || !identifier2.safeParse(client.provider).success || typeof client.generate !== "function" || typeof client.capabilities !== "function") {
33912
33922
  throw new TypeError("Invalid ModelClient");
33913
33923
  }
33924
+ const pending = new Set;
33925
+ const track = (promise) => {
33926
+ pending.add(promise);
33927
+ promise.then(() => pending.delete(promise), () => pending.delete(promise));
33928
+ return promise;
33929
+ };
33914
33930
  const readCapabilities = async () => {
33915
- const value = await client.capabilities();
33931
+ const value = await track(client.capabilities());
33916
33932
  if (!value || typeof value.toolUse !== "boolean")
33917
33933
  return failure("invalid", "invalid_response");
33918
- return { status: "available", toolUse: value.toolUse, transportCancellation: false };
33934
+ return {
33935
+ status: "available",
33936
+ toolUse: value.toolUse,
33937
+ transportCancellation: value.transportCancellation === true
33938
+ };
33919
33939
  };
33920
33940
  return {
33921
33941
  provider: client.provider,
33942
+ async drain({ timeoutMs }) {
33943
+ if (!timeoutSchema.safeParse(timeoutMs).success)
33944
+ return { pendingOperations: pending.size };
33945
+ await bounded(() => Promise.allSettled([...pending]), timeoutMs);
33946
+ return { pendingOperations: pending.size };
33947
+ },
33922
33948
  async capabilities(options, signal) {
33923
33949
  if (!timeoutSchema.safeParse(options?.timeoutMs).success)
33924
33950
  return failure("invalid", "invalid_request");
@@ -33927,11 +33953,11 @@ function createModelClientTarget(client) {
33927
33953
  async turn(request, signal) {
33928
33954
  let parsed;
33929
33955
  try {
33930
- parsed = requestSchema.safeParse(request);
33956
+ parsed = agentTurnRequestSchema.safeParse(request);
33931
33957
  } catch {
33932
33958
  return failure("invalid", "invalid_request");
33933
33959
  }
33934
- if (!parsed.success || !validTranscript(parsed.data.messages))
33960
+ if (!parsed.success || !validWorkflowTranscript(parsed.data.messages))
33935
33961
  return failure("invalid", "invalid_request");
33936
33962
  const input = parsed.data;
33937
33963
  const validators4 = new Map;
@@ -33952,6 +33978,12 @@ function createModelClientTarget(client) {
33952
33978
  return failure("invalid", "invalid_request");
33953
33979
  }
33954
33980
  const started = Date.now();
33981
+ const controller = new AbortController;
33982
+ const abort = () => controller.abort();
33983
+ signal?.addEventListener("abort", abort, { once: true });
33984
+ if (signal?.aborted)
33985
+ controller.abort();
33986
+ const timer = setTimeout(abort, input.budgets.timeoutMs);
33955
33987
  return bounded(async () => {
33956
33988
  const capabilities = await readCapabilities();
33957
33989
  if (capabilities.status !== "available")
@@ -33963,12 +33995,14 @@ function createModelClientTarget(client) {
33963
33995
  if (Date.now() - started >= input.budgets.timeoutMs)
33964
33996
  return failure("error", "timeout");
33965
33997
  const options = {
33998
+ maxRetries: 0,
33966
33999
  prompt: input.messages,
33967
34000
  tools: input.tools,
33968
34001
  model: input.model,
33969
- ...input.generation
34002
+ ...input.generation,
34003
+ ...capabilities.transportCancellation ? { signal: controller.signal } : {}
33970
34004
  };
33971
- const generated = resultSchema.safeParse(await client.generate(options));
34005
+ const generated = resultSchema.safeParse(await track(client.generate(options)));
33972
34006
  if (!generated.success)
33973
34007
  return failure("invalid", "invalid_response");
33974
34008
  const result = generated.data;
@@ -33976,17 +34010,29 @@ function createModelClientTarget(client) {
33976
34010
  if (result.functionCall !== undefined || result.finishReason === "function_call" || result.finishReason === "tool_calls" && calls.length === 0 || calls.length > input.budgets.maxToolCalls || result.tokens.total !== result.tokens.prompt + result.tokens.completion)
33977
34011
  return failure("invalid", "invalid_response");
33978
34012
  const ids = new Set(input.messages.flatMap((message) => message.tool_calls?.map((call) => call.id) ?? []));
34013
+ const rejected = (call, reason) => ({
34014
+ ...failure("invalid", "invalid_response"),
34015
+ tokens: result.tokens,
34016
+ ...result.usageAvailable !== undefined ? { usageAvailable: result.usageAvailable } : {},
34017
+ rejectedCall: {
34018
+ requestedCallIdHash: createHash3("sha256").update(call.id).digest("hex"),
34019
+ tool: getWorkflowTool(call.function.name)?.id ?? "unknown",
34020
+ reason
34021
+ }
34022
+ });
33979
34023
  for (const call of calls) {
33980
34024
  if (ids.has(call.id))
33981
- return failure("invalid", "invalid_response");
34025
+ return rejected(call, "duplicate_id");
33982
34026
  ids.add(call.id);
33983
34027
  const validate2 = validators4.get(call.function.name);
34028
+ if (!validate2)
34029
+ return rejected(call, "undeclared_tool");
33984
34030
  try {
33985
34031
  const args = JSON.parse(call.function.arguments);
33986
34032
  if (!args || typeof args !== "object" || Array.isArray(args) || !validate2 || validate2(args) !== true)
33987
- return failure("invalid", "invalid_response");
34033
+ return rejected(call, "invalid_arguments");
33988
34034
  } catch {
33989
- return failure("invalid", "invalid_response");
34035
+ return rejected(call, "invalid_arguments");
33990
34036
  }
33991
34037
  }
33992
34038
  return {
@@ -33999,15 +34045,1301 @@ function createModelClientTarget(client) {
33999
34045
  ...calls.length ? { tool_calls: calls } : {}
34000
34046
  },
34001
34047
  tokens: result.tokens,
34048
+ ...result.usageAvailable !== undefined ? { usageAvailable: result.usageAvailable } : {},
34002
34049
  latencyMs: result.latencyMs,
34003
34050
  finishReason: result.finishReason
34004
34051
  };
34005
- }, input.budgets.timeoutMs, signal);
34052
+ }, input.budgets.timeoutMs, signal).then((result) => controller.signal.aborted ? failure("error", signal?.aborted ? "aborted" : "timeout") : result).finally(() => {
34053
+ clearTimeout(timer);
34054
+ signal?.removeEventListener("abort", abort);
34055
+ });
34006
34056
  }
34007
34057
  };
34008
34058
  }
34009
- // src/validator/validator.ts
34059
+ // src/agent-workflow/environment.ts
34010
34060
  var import_yaml3 = __toESM(require_dist(), 1);
34061
+ import { constants } from "node:fs";
34062
+ import { lstat, open, realpath } from "node:fs/promises";
34063
+ import { join as join3, resolve as resolve3 } from "node:path";
34064
+ class WorkflowEnvironmentInitializationError extends Error {
34065
+ cleanup;
34066
+ constructor(cleanup) {
34067
+ super("environment_initialization_failed");
34068
+ this.cleanup = cleanup;
34069
+ }
34070
+ }
34071
+ function isWorkflowState(value) {
34072
+ return isWorkflowJson(value) && value !== null && typeof value === "object" && !Array.isArray(value);
34073
+ }
34074
+ function workflowPathAllowed(workflow, tool, input) {
34075
+ if (tool !== "read_file" && tool !== "write_file")
34076
+ return true;
34077
+ if (!isWorkflowState(input) || typeof input.path !== "string" || !isWorkflowRelativePath(input.path))
34078
+ return false;
34079
+ const paths = workflow.environment.policy.paths;
34080
+ return !paths || (tool === "read_file" ? paths.read : paths.write).includes(input.path);
34081
+ }
34082
+ function workflowToolPermitted(workflow, tool) {
34083
+ const descriptor2 = getWorkflowTool(tool);
34084
+ if (!descriptor2 || !workflow.tools.includes(descriptor2.id))
34085
+ return false;
34086
+ if (descriptor2.authority.access === "none")
34087
+ return true;
34088
+ const grant = workflow.environment.policy.permissions[descriptor2.authority.resource];
34089
+ return grant === "write" || grant === "read" && descriptor2.authority.access === "read";
34090
+ }
34091
+ async function resolveWorkflowInitialState(workflow, fixtureRoot) {
34092
+ const initial = workflow.workflow.initial_state;
34093
+ if (typeof initial !== "string") {
34094
+ if (!isWorkflowState(initial))
34095
+ throw new Error("invalid_fixture");
34096
+ return structuredClone(initial);
34097
+ }
34098
+ if (!fixtureRoot || !isWorkflowRelativePath(initial) || !/\.(json|ya?ml)$/i.test(initial) || initial.split("/").some((part) => part.startsWith(".") || /(?:^|[._-])(?:secrets?|credentials?|tokens?|private|id_rsa|id_ed25519)(?:[._-]|$)/i.test(part)))
34099
+ throw new Error("invalid_fixture");
34100
+ const root = resolve3(fixtureRoot);
34101
+ const canonicalRoot = await realpath(root);
34102
+ let current = canonicalRoot;
34103
+ const ancestry = [];
34104
+ const parts = initial.split("/");
34105
+ for (const [index, part] of parts.entries()) {
34106
+ current = join3(current, part);
34107
+ const stat2 = await lstat(current);
34108
+ if (stat2.isSymbolicLink() || (index < parts.length - 1 ? !stat2.isDirectory() : !stat2.isFile()))
34109
+ throw new Error("invalid_fixture");
34110
+ ancestry.push({ path: current, ino: stat2.ino, dev: stat2.dev });
34111
+ }
34112
+ const file = await open(current, constants.O_RDONLY | constants.O_NOFOLLOW);
34113
+ try {
34114
+ const stat2 = await file.stat();
34115
+ const expected = ancestry[ancestry.length - 1];
34116
+ if (!stat2.isFile() || stat2.size > 1048576 || stat2.ino !== expected.ino || stat2.dev !== expected.dev)
34117
+ throw new Error("invalid_fixture");
34118
+ const buffer = Buffer.alloc(1048577);
34119
+ let length = 0;
34120
+ while (length < buffer.length) {
34121
+ const { bytesRead } = await file.read(buffer, length, buffer.length - length, null);
34122
+ if (!bytesRead)
34123
+ break;
34124
+ length += bytesRead;
34125
+ }
34126
+ if (length > 1048576)
34127
+ throw new Error("invalid_fixture");
34128
+ for (const entry of ancestry) {
34129
+ const after = await lstat(entry.path);
34130
+ if (after.isSymbolicLink() || after.ino !== entry.ino || after.dev !== entry.dev)
34131
+ throw new Error("invalid_fixture");
34132
+ }
34133
+ const document2 = import_yaml3.parseDocument(buffer.subarray(0, length).toString("utf8"), {
34134
+ uniqueKeys: true,
34135
+ customTags: []
34136
+ });
34137
+ if (document2.errors.length || document2.warnings.length)
34138
+ throw new Error("invalid_fixture");
34139
+ const value = document2.toJS({ maxAliasCount: 0 });
34140
+ if (!isWorkflowState(value))
34141
+ throw new Error("invalid_fixture");
34142
+ return value;
34143
+ } finally {
34144
+ await file.close();
34145
+ }
34146
+ }
34147
+ var createSimulatedWorkflowEnvironment = async ({
34148
+ workflow,
34149
+ initialState,
34150
+ signal
34151
+ }) => {
34152
+ if (workflow.environment.type !== "simulated" || signal.aborted || !isWorkflowState(initialState))
34153
+ throw new Error("environment_unavailable");
34154
+ const configuration = structuredClone(workflow);
34155
+ let state = structuredClone(initialState);
34156
+ let closed = false;
34157
+ return {
34158
+ type: "simulated",
34159
+ capabilities: {
34160
+ network: "denied",
34161
+ commands: "denied",
34162
+ externalSideEffects: "denied",
34163
+ isolation: "memory"
34164
+ },
34165
+ async execute(request, executionSignal) {
34166
+ if (closed || executionSignal.aborted)
34167
+ throw new Error("environment_unavailable");
34168
+ if (!workflowPathAllowed(configuration, request.tool, request.input))
34169
+ return {
34170
+ status: "denied",
34171
+ code: "permission_denied",
34172
+ evidence: {
34173
+ tool: getWorkflowTool(request.tool)?.id ?? "unknown",
34174
+ version: "1",
34175
+ status: "denied",
34176
+ code: "permission_denied"
34177
+ }
34178
+ };
34179
+ const result = executeSimulatedTool({
34180
+ ...request,
34181
+ state,
34182
+ policy: configuration.environment.policy,
34183
+ declaredTools: configuration.tools
34184
+ });
34185
+ if (result.status === "succeeded")
34186
+ state = structuredClone(result.state);
34187
+ return result;
34188
+ },
34189
+ async snapshot(snapshotSignal) {
34190
+ if (closed || snapshotSignal.aborted)
34191
+ throw new Error("environment_unavailable");
34192
+ return structuredClone(state);
34193
+ },
34194
+ async close() {
34195
+ closed = true;
34196
+ state = {};
34197
+ return { status: "completed", artifacts: "discarded" };
34198
+ }
34199
+ };
34200
+ };
34201
+ // src/agent-workflow/sandbox.ts
34202
+ import { spawn } from "node:child_process";
34203
+ import { randomUUID } from "node:crypto";
34204
+ var WORKFLOW_SANDBOX_IMAGE = "oven/bun:1.3.10-alpine";
34205
+ var LIMIT = 1048576;
34206
+ var OWNER_LABEL = "artemiskit.workflow.owner";
34207
+ var FILE_PROGRAM = String.raw`
34208
+ const fs = require('node:fs');
34209
+ const root = '/workspace';
34210
+ const limit = 1048576;
34211
+ const safe = p => typeof p === 'string' && p.length <= 512 && /^[A-Za-z0-9_-][A-Za-z0-9_./-]*$/.test(p) && p.split('/').every(x => x && !['.','..','__proto__','prototype','constructor'].includes(x));
34212
+ function checked(p, create=false) {
34213
+ if (!safe(p)) throw 'invalid_input';
34214
+ const parts = p.split('/'); let current = root;
34215
+ for (let i=0;i<parts.length;i++) {
34216
+ current += '/' + parts[i];
34217
+ let stat;
34218
+ try { stat = fs.lstatSync(current); } catch(e) { if(e.code !== 'ENOENT') throw 'tool_error'; }
34219
+ if (stat && (stat.isSymbolicLink() || (i<parts.length-1 ? !stat.isDirectory() : !stat.isFile()))) throw 'invalid_input';
34220
+ if (!stat && i<parts.length-1) { if(!create) throw 'not_found'; fs.mkdirSync(current, {mode: 0o700}); }
34221
+ }
34222
+ return current;
34223
+ }
34224
+ function write(p, content) {
34225
+ if(typeof content !== 'string' || content.length>16384) throw 'invalid_input';
34226
+ const path=checked(p,true); const fd=fs.openSync(path, fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_TRUNC|fs.constants.O_NOFOLLOW,0o600);
34227
+ try { fs.writeFileSync(fd,content,'utf8'); } finally { fs.closeSync(fd); }
34228
+ }
34229
+ function read(p) {
34230
+ const path=checked(p); let fd;
34231
+ try { fd=fs.openSync(path,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW); } catch(e) { if(e.code==='ENOENT') throw 'not_found'; throw 'tool_error'; }
34232
+ try { const stat=fs.fstatSync(fd); if(!stat.isFile() || stat.size>65536) throw 'output_limit'; const text=fs.readFileSync(fd,'utf8'); if(text.length>16384) throw 'output_limit'; return text; } finally { fs.closeSync(fd); }
34233
+ }
34234
+ function snapshot() {
34235
+ const files={}; let count=0, bytes=0;
34236
+ function walk(dir, prefix, depth) {
34237
+ if(depth>16) throw 'output_limit';
34238
+ for(const name of fs.readdirSync(dir).sort()) {
34239
+ const p=prefix ? prefix+'/'+name : name;
34240
+ if(!safe(p) || ++count>1000) throw 'output_limit';
34241
+ const stat=fs.lstatSync(dir+'/'+name);
34242
+ if(stat.isSymbolicLink()) throw 'invalid_input';
34243
+ if(stat.isDirectory()) walk(dir+'/'+name,p,depth+1);
34244
+ else if(stat.isFile()) { const value=read(p); bytes+=Buffer.byteLength(value)+Buffer.byteLength(p); if(bytes>limit) throw 'output_limit'; files[p]=value; }
34245
+ else throw 'invalid_input';
34246
+ }
34247
+ }
34248
+ walk(root,'',0); return files;
34249
+ }
34250
+ try {
34251
+ const raw=await Bun.stdin.text(); if(Buffer.byteLength(raw)>limit) throw 'invalid_input';
34252
+ const request=JSON.parse(raw); let output;
34253
+ if(request.action==='init') { for(const [path,content] of Object.entries(request.files)) write(path,content); }
34254
+ else if(request.action==='write') { write(request.path,request.content); output={path:request.path,written:true}; }
34255
+ else if(request.action==='read') output={content:read(request.path)};
34256
+ else if(request.action!=='snapshot') throw 'invalid_input';
34257
+ const result=JSON.stringify({ok:true,files:snapshot(),...(output?{output}:{})}); if(Buffer.byteLength(result)>limit) throw 'output_limit'; process.stdout.write(result);
34258
+ } catch(error) { process.stdout.write(JSON.stringify({ok:false,code:['invalid_input','not_found','output_limit'].includes(error)?error:'tool_error'})); process.exitCode=1; }
34259
+ `;
34260
+
34261
+ class DockerFailure extends Error {
34262
+ code;
34263
+ uncertain;
34264
+ constructor(code, uncertain = false) {
34265
+ super(`sandbox_${code}`);
34266
+ this.code = code;
34267
+ this.uncertain = uncertain;
34268
+ }
34269
+ }
34270
+ function createDockerWorkflowEnvironmentFactory(options = {}) {
34271
+ const operationTimeoutMs = options.operationTimeoutMs ?? 5000;
34272
+ const cleanupTimeoutMs = options.cleanupTimeoutMs ?? 3000;
34273
+ if (Object.keys(options).some((key) => !["operationTimeoutMs", "cleanupTimeoutMs"].includes(key)) || !Number.isInteger(operationTimeoutMs) || operationTimeoutMs < 1 || operationTimeoutMs > 30000 || !Number.isInteger(cleanupTimeoutMs) || cleanupTimeoutMs < 1 || cleanupTimeoutMs > 1e4)
34274
+ throw new TypeError("Invalid Docker workflow environment options");
34275
+ return async ({ workflow, initialState, signal }) => {
34276
+ const parsed = AgentWorkflowSchema.safeParse(workflow);
34277
+ if (!parsed.success || parsed.data.environment.type !== "sandbox" || !isWorkflowState(initialState) || signal.aborted)
34278
+ throw new WorkflowEnvironmentInitializationError({
34279
+ status: "completed",
34280
+ artifacts: "discarded",
34281
+ pendingOperations: 0
34282
+ });
34283
+ const configuration = parsed.data;
34284
+ let state = structuredClone(initialState);
34285
+ const files = state.files ?? {};
34286
+ if (!isWorkflowState(files) || Object.entries(files).some(([path, content]) => !isWorkflowRelativePath(path) || typeof content !== "string" || content.length > 16384) || Object.keys(files).length > 1000)
34287
+ throw new WorkflowEnvironmentInitializationError({
34288
+ status: "completed",
34289
+ artifacts: "discarded",
34290
+ pendingOperations: 0
34291
+ });
34292
+ const owner = randomUUID();
34293
+ const name = `artemiskit-workflow-${owner}`;
34294
+ const children = new Set;
34295
+ let closed = false;
34296
+ let closing = false;
34297
+ let creationAttempted = false;
34298
+ let creationUncertain = false;
34299
+ let busy = false;
34300
+ const command = (args, input, commandSignal, timeoutMs = operationTimeoutMs) => {
34301
+ if (commandSignal.aborted)
34302
+ return Promise.reject(new DockerFailure("aborted"));
34303
+ if (Buffer.byteLength(input) > LIMIT)
34304
+ return Promise.reject(new DockerFailure("output_limit"));
34305
+ return new Promise((resolve4, reject) => {
34306
+ let child;
34307
+ try {
34308
+ child = spawn("docker", args, { stdio: ["pipe", "pipe", "pipe"] });
34309
+ } catch {
34310
+ reject(new DockerFailure("unavailable"));
34311
+ return;
34312
+ }
34313
+ children.add(child);
34314
+ const output = [];
34315
+ let size = 0;
34316
+ let failure2;
34317
+ let settled = false;
34318
+ const stop = (error2) => {
34319
+ failure2 ??= error2;
34320
+ child.kill("SIGKILL");
34321
+ };
34322
+ const abort = () => stop(new DockerFailure("aborted", true));
34323
+ const timer = setTimeout(() => stop(new DockerFailure("timeout", true)), timeoutMs);
34324
+ commandSignal.addEventListener("abort", abort, { once: true });
34325
+ child.stdout?.on("data", (chunk) => {
34326
+ size += chunk.length;
34327
+ if (size > LIMIT)
34328
+ stop(new DockerFailure("output_limit", true));
34329
+ else
34330
+ output.push(Buffer.from(chunk));
34331
+ });
34332
+ child.stderr?.on("data", (chunk) => {
34333
+ size += chunk.length;
34334
+ if (size > LIMIT)
34335
+ stop(new DockerFailure("output_limit", true));
34336
+ });
34337
+ child.stdin?.on("error", () => {});
34338
+ const finish = (code, error2) => {
34339
+ if (settled)
34340
+ return;
34341
+ settled = true;
34342
+ clearTimeout(timer);
34343
+ commandSignal.removeEventListener("abort", abort);
34344
+ children.delete(child);
34345
+ if (error2 || failure2)
34346
+ reject(error2 ?? failure2);
34347
+ else
34348
+ resolve4({ code: code ?? 1, stdout: Buffer.concat(output).toString("utf8") });
34349
+ };
34350
+ child.once("error", () => finish(null, new DockerFailure("unavailable")));
34351
+ child.once("close", (code) => finish(code));
34352
+ child.stdin?.end(input);
34353
+ if (commandSignal.aborted)
34354
+ abort();
34355
+ });
34356
+ };
34357
+ async function close(closeSignal) {
34358
+ if (closed)
34359
+ return { status: "completed", artifacts: "discarded" };
34360
+ closing = true;
34361
+ for (const child of children)
34362
+ child.kill("SIGKILL");
34363
+ const cleanupController = new AbortController;
34364
+ const onAbort = () => cleanupController.abort();
34365
+ closeSignal.addEventListener("abort", onAbort, { once: true });
34366
+ if (closeSignal.aborted)
34367
+ cleanupController.abort();
34368
+ const timer = setTimeout(onAbort, cleanupTimeoutMs);
34369
+ try {
34370
+ if (!creationAttempted) {
34371
+ closed = true;
34372
+ state = {};
34373
+ return { status: "completed", artifacts: "discarded" };
34374
+ }
34375
+ const inspected = await command(["inspect", "--format", `{{index .Config.Labels "${OWNER_LABEL}"}}`, name], "", cleanupController.signal, cleanupTimeoutMs);
34376
+ if (inspected.code === 0) {
34377
+ if (inspected.stdout.trim() !== owner)
34378
+ return { status: "unresolved", artifacts: "unknown" };
34379
+ const removed = await command(["rm", "--force", name], "", cleanupController.signal, cleanupTimeoutMs);
34380
+ if (removed.code !== 0)
34381
+ return { status: "unresolved", artifacts: "unknown" };
34382
+ creationUncertain = false;
34383
+ }
34384
+ const remaining = await command(["ps", "--all", "--filter", `name=^/${name}$`, "--format", "{{.Names}}"], "", cleanupController.signal, cleanupTimeoutMs);
34385
+ if (remaining.code !== 0 || remaining.stdout.trim() || creationUncertain || children.size > 0)
34386
+ return { status: "unresolved", artifacts: "unknown" };
34387
+ closed = true;
34388
+ state = {};
34389
+ return { status: "completed", artifacts: "discarded" };
34390
+ } catch {
34391
+ return { status: "unresolved", artifacts: "unknown" };
34392
+ } finally {
34393
+ clearTimeout(timer);
34394
+ closeSignal.removeEventListener("abort", onAbort);
34395
+ }
34396
+ }
34397
+ async function fileOperation(request, operationSignal) {
34398
+ const result = await command(["exec", "--interactive", name, "bun", "--eval", FILE_PROGRAM], JSON.stringify(request), operationSignal);
34399
+ let response;
34400
+ try {
34401
+ response = JSON.parse(result.stdout);
34402
+ } catch {
34403
+ throw new DockerFailure("unavailable");
34404
+ }
34405
+ if (!isWorkflowState(response))
34406
+ throw new DockerFailure("unavailable");
34407
+ if (response.ok === false && ["invalid_input", "not_found", "output_limit", "tool_error"].includes(String(response.code)))
34408
+ return {
34409
+ ok: false,
34410
+ code: response.code
34411
+ };
34412
+ if (result.code !== 0 || response.ok !== true || !isWorkflowState(response.files) || Object.entries(response.files).some(([path, content]) => !isWorkflowRelativePath(path) || typeof content !== "string" || content.length > 16384))
34413
+ throw new DockerFailure("unavailable");
34414
+ const next = { ...state, files: response.files };
34415
+ if (!isWorkflowState(next))
34416
+ throw new DockerFailure("output_limit");
34417
+ state = structuredClone(next);
34418
+ return { ok: true, output: response.output };
34419
+ }
34420
+ try {
34421
+ const image = await command(["image", "inspect", WORKFLOW_SANDBOX_IMAGE, "--format", "{{.Id}}"], "", signal);
34422
+ if (image.code !== 0 || !/^sha256:[a-f0-9]{64}\s*$/.test(image.stdout))
34423
+ throw new DockerFailure("unavailable");
34424
+ creationAttempted = true;
34425
+ let created;
34426
+ try {
34427
+ created = await command([
34428
+ "create",
34429
+ "--pull=never",
34430
+ "--name",
34431
+ name,
34432
+ "--label",
34433
+ "artemiskit.workflow=true",
34434
+ "--label",
34435
+ `${OWNER_LABEL}=${owner}`,
34436
+ "--network",
34437
+ "none",
34438
+ "--read-only",
34439
+ "--cap-drop",
34440
+ "ALL",
34441
+ "--security-opt",
34442
+ "no-new-privileges",
34443
+ "--memory",
34444
+ "128m",
34445
+ "--memory-swap",
34446
+ "128m",
34447
+ "--cpus",
34448
+ "0.5",
34449
+ "--pids-limit",
34450
+ "64",
34451
+ "--user",
34452
+ "1000:1000",
34453
+ "--tmpfs",
34454
+ "/workspace:rw,noexec,nosuid,nodev,size=16777216,uid=1000,gid=1000,mode=0700",
34455
+ "--tmpfs",
34456
+ "/tmp:rw,noexec,nosuid,nodev,size=8388608,uid=1000,gid=1000,mode=0700",
34457
+ "--workdir",
34458
+ "/workspace",
34459
+ "--entrypoint",
34460
+ "bun",
34461
+ image.stdout.trim(),
34462
+ "--eval",
34463
+ "setInterval(() => {}, 1000)"
34464
+ ], "", signal);
34465
+ } catch (error2) {
34466
+ creationUncertain = error2 instanceof DockerFailure && error2.uncertain;
34467
+ throw error2;
34468
+ }
34469
+ if (created.code !== 0)
34470
+ throw new DockerFailure("unavailable");
34471
+ const started = await command(["start", name], "", signal);
34472
+ if (started.code !== 0)
34473
+ throw new DockerFailure("unavailable");
34474
+ const initialized2 = await fileOperation({ action: "init", files }, signal);
34475
+ if (!initialized2.ok)
34476
+ throw new DockerFailure("unavailable");
34477
+ } catch {
34478
+ const cleanup = await close(new AbortController().signal);
34479
+ throw new WorkflowEnvironmentInitializationError({
34480
+ ...cleanup,
34481
+ pendingOperations: children.size + (creationUncertain ? 1 : 0)
34482
+ });
34483
+ }
34484
+ return {
34485
+ type: "sandbox",
34486
+ capabilities: {
34487
+ network: "denied",
34488
+ commands: "denied",
34489
+ externalSideEffects: "denied",
34490
+ isolation: "container"
34491
+ },
34492
+ async execute(request, operationSignal) {
34493
+ const tool = getWorkflowTool(request.tool)?.id ?? "unknown";
34494
+ const failure2 = (status, code) => ({
34495
+ status,
34496
+ code,
34497
+ evidence: { tool, version: "1", status, code }
34498
+ });
34499
+ if (closed || closing || busy || operationSignal.aborted || signal.aborted)
34500
+ return failure2("failed", "tool_error");
34501
+ const checked = executeSimulatedTool({
34502
+ tool: request.tool,
34503
+ input: request.input,
34504
+ state,
34505
+ policy: configuration.environment.policy,
34506
+ declaredTools: configuration.tools
34507
+ });
34508
+ if (checked.status === "invalid" && checked.code === "invalid_input")
34509
+ return checked;
34510
+ if (!workflowPathAllowed(configuration, request.tool, request.input))
34511
+ return failure2("denied", "permission_denied");
34512
+ if (checked.status !== "succeeded")
34513
+ return checked;
34514
+ busy = true;
34515
+ try {
34516
+ if (request.tool === "read_file" || request.tool === "write_file") {
34517
+ if (!isWorkflowState(request.input))
34518
+ return failure2("invalid", "invalid_input");
34519
+ const result = await fileOperation({ action: request.tool === "read_file" ? "read" : "write", ...request.input }, operationSignal);
34520
+ if (!result.ok)
34521
+ return failure2(result.code === "invalid_input" ? "invalid" : "failed", result.code);
34522
+ if (!isWorkflowJson(result.output))
34523
+ return failure2("failed", "tool_error");
34524
+ return {
34525
+ status: "succeeded",
34526
+ output: structuredClone(result.output),
34527
+ state: structuredClone(state),
34528
+ evidence: { tool, version: "1", status: "succeeded" }
34529
+ };
34530
+ }
34531
+ state = structuredClone(checked.state);
34532
+ return checked;
34533
+ } catch {
34534
+ return failure2("failed", "tool_error");
34535
+ } finally {
34536
+ busy = false;
34537
+ }
34538
+ },
34539
+ async snapshot(snapshotSignal) {
34540
+ if (closed || closing || busy || snapshotSignal.aborted)
34541
+ throw new DockerFailure("unavailable");
34542
+ busy = true;
34543
+ try {
34544
+ const result = await fileOperation({ action: "snapshot" }, snapshotSignal);
34545
+ if (!result.ok)
34546
+ throw new DockerFailure("unavailable");
34547
+ return structuredClone(state);
34548
+ } finally {
34549
+ busy = false;
34550
+ }
34551
+ },
34552
+ close
34553
+ };
34554
+ };
34555
+ }
34556
+ var createDockerWorkflowEnvironment = createDockerWorkflowEnvironmentFactory();
34557
+ // src/agent-workflow/session.ts
34558
+ var import_ajv5 = __toESM(require_ajv(), 1);
34559
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
34560
+ var hash = (value) => createHash4("sha256").update(value).digest("hex");
34561
+ function identity(value) {
34562
+ return {
34563
+ sha256: hash(value),
34564
+ .../^[A-Za-z][A-Za-z0-9._:/-]{0,127}$/.test(value) && !/(?:secret|token|password|credential|api.?key|^sk-|^npm_)/i.test(value) ? { display: value } : {}
34565
+ };
34566
+ }
34567
+ var zeroUsage = () => ({ prompt: 0, completion: 0, total: 0 });
34568
+ var usageSchema = exports_external.object({
34569
+ prompt: exports_external.number().int().nonnegative().safe(),
34570
+ completion: exports_external.number().int().nonnegative().safe(),
34571
+ total: exports_external.number().int().nonnegative().safe()
34572
+ }).strict();
34573
+ var callSchema = exports_external.object({
34574
+ id: exports_external.string().min(1).max(256),
34575
+ type: exports_external.literal("function"),
34576
+ function: exports_external.object({
34577
+ name: exports_external.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/),
34578
+ arguments: exports_external.string().max(1e6)
34579
+ }).strict()
34580
+ }).strict();
34581
+ var completedSchema = exports_external.object({
34582
+ status: exports_external.literal("completed"),
34583
+ id: exports_external.string().min(1).max(256),
34584
+ model: exports_external.string().min(1).max(256),
34585
+ message: exports_external.object({
34586
+ role: exports_external.literal("assistant"),
34587
+ content: exports_external.string().max(1e6),
34588
+ tool_calls: exports_external.array(callSchema).max(100).optional()
34589
+ }).strict(),
34590
+ tokens: usageSchema,
34591
+ usageAvailable: exports_external.boolean().optional(),
34592
+ latencyMs: exports_external.number().finite().nonnegative(),
34593
+ finishReason: exports_external.enum(["stop", "length", "tool_calls", "content_filter"]).optional()
34594
+ }).strict();
34595
+ var failureSchema = exports_external.object({
34596
+ status: exports_external.enum(["unsupported", "invalid", "error"]),
34597
+ tokens: usageSchema.optional(),
34598
+ usageAvailable: exports_external.boolean().optional(),
34599
+ rejectedCall: exports_external.object({
34600
+ requestedCallIdHash: exports_external.string().regex(/^[a-f0-9]{64}$/),
34601
+ tool: exports_external.string(),
34602
+ reason: exports_external.enum(["undeclared_tool", "invalid_arguments", "duplicate_id"])
34603
+ }).strict().optional(),
34604
+ code: exports_external.enum([
34605
+ "invalid_request",
34606
+ "tool_use_unsupported",
34607
+ "invalid_response",
34608
+ "target_error",
34609
+ "timeout",
34610
+ "aborted"
34611
+ ])
34612
+ }).strict();
34613
+ var capabilitySchema = exports_external.object({
34614
+ status: exports_external.literal("available"),
34615
+ toolUse: exports_external.boolean(),
34616
+ transportCancellation: exports_external.boolean()
34617
+ }).strict();
34618
+ var cleanupSchema = exports_external.object({
34619
+ status: exports_external.enum(["completed", "unresolved"]),
34620
+ artifacts: exports_external.enum(["discarded", "retained", "unknown"])
34621
+ }).strict();
34622
+ var failureCodes = new Set([
34623
+ "undeclared_tool",
34624
+ "permission_denied",
34625
+ "invalid_input",
34626
+ "invalid_state",
34627
+ "not_found",
34628
+ "output_limit",
34629
+ "invalid_policy",
34630
+ "tool_error"
34631
+ ]);
34632
+ function safeBoundary(value) {
34633
+ let nodes = 0;
34634
+ let bytes = 0;
34635
+ const parents = new Set;
34636
+ function check(item, depth) {
34637
+ if (++nodes > 1e4 || depth > 16)
34638
+ return false;
34639
+ if (item === undefined || item === null || typeof item === "boolean")
34640
+ return true;
34641
+ if (typeof item === "number")
34642
+ return Number.isFinite(item);
34643
+ if (typeof item === "string") {
34644
+ bytes += Buffer.byteLength(item);
34645
+ return bytes <= 1048576;
34646
+ }
34647
+ if (typeof item !== "object" || parents.has(item) || !Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null)
34648
+ return false;
34649
+ if (Object.getOwnPropertySymbols(item).length || Array.isArray(item) && item.length > 1e4)
34650
+ return false;
34651
+ parents.add(item);
34652
+ for (const key of Object.getOwnPropertyNames(item)) {
34653
+ if (Array.isArray(item) && key === "length")
34654
+ continue;
34655
+ bytes += Buffer.byteLength(key);
34656
+ const descriptor2 = Object.getOwnPropertyDescriptor(item, key);
34657
+ if (bytes > 1048576 || ["__proto__", "constructor", "prototype"].includes(key) || !descriptor2 || !("value" in descriptor2) || !descriptor2.enumerable || !check(descriptor2.value, depth + 1))
34658
+ return false;
34659
+ }
34660
+ parents.delete(item);
34661
+ return true;
34662
+ }
34663
+ try {
34664
+ return check(value, 0);
34665
+ } catch {
34666
+ return false;
34667
+ }
34668
+ }
34669
+
34670
+ class Stop extends Error {
34671
+ execution;
34672
+ reason;
34673
+ constructor(execution, reason) {
34674
+ super(reason);
34675
+ this.execution = execution;
34676
+ this.reason = reason;
34677
+ }
34678
+ }
34679
+ function createAgentWorkflowSession(options) {
34680
+ const controller = new AbortController;
34681
+ let sessionState = "idle";
34682
+ let promise;
34683
+ let workflow;
34684
+ try {
34685
+ workflow = AgentWorkflowSchema.parse(options.workflow);
34686
+ } catch {}
34687
+ const target = options.target;
34688
+ const retained = [];
34689
+ const listeners = new Set;
34690
+ let finished = false;
34691
+ let started = 0;
34692
+ let eventSequence = 0;
34693
+ const record = {
34694
+ schemaVersion: "1",
34695
+ engine: "native",
34696
+ execution: "invalid",
34697
+ reason: "invalid_workflow",
34698
+ policy: "passed",
34699
+ ...workflow ? {
34700
+ configuration: {
34701
+ sha256: hash(JSON.stringify(workflow)),
34702
+ provider: identity(workflow.target.provider),
34703
+ model: identity(workflow.target.model),
34704
+ generation: {
34705
+ maxTokens: workflow.target.generation?.max_tokens ?? 1024,
34706
+ temperature: workflow.target.generation?.temperature ?? 0
34707
+ },
34708
+ limits: structuredClone(workflow.environment.policy.budgets)
34709
+ }
34710
+ } : {},
34711
+ taskVerification: "unavailable",
34712
+ environment: workflow?.environment.type ?? "unknown",
34713
+ capability: {
34714
+ advertised: null,
34715
+ transportCancellation: false,
34716
+ preflight: options.preflight || options.preflightOnly ? "failed" : "not_requested"
34717
+ },
34718
+ usage: {
34719
+ status: "unavailable",
34720
+ reported: zeroUsage(),
34721
+ missingRequests: 0,
34722
+ inFlightUnknown: false,
34723
+ preflight: zeroUsage()
34724
+ },
34725
+ budgets: {
34726
+ actions: 0,
34727
+ modelRequests: 0,
34728
+ toolCalls: 0,
34729
+ modelRequestAccounting: "target_invocations",
34730
+ transportAttempts: "unavailable",
34731
+ tokenOvershoot: 0,
34732
+ elapsedMs: 0
34733
+ },
34734
+ cleanup: { status: "completed", artifacts: "discarded", pendingOperations: 0 },
34735
+ artifacts: { state: "unavailable" },
34736
+ events: retained,
34737
+ droppedEvents: 0
34738
+ };
34739
+ const pending = new Set;
34740
+ const modelPending = new Set;
34741
+ let transcript = [];
34742
+ let state = null;
34743
+ let environment;
34744
+ let deadlineExpired = false;
34745
+ let phase = "execution";
34746
+ let measuredRequests = 0;
34747
+ const ids = new Set;
34748
+ const emit = (event) => {
34749
+ const value = {
34750
+ ...event,
34751
+ sequence: ++eventSequence,
34752
+ elapsedMs: Math.max(0, Date.now() - started),
34753
+ phase
34754
+ };
34755
+ if (retained.length < 255 || value.type === "finished")
34756
+ retained.push(value);
34757
+ else
34758
+ record.droppedEvents++;
34759
+ try {
34760
+ options.onEvent?.(structuredClone(value));
34761
+ } catch {}
34762
+ for (const wake of listeners)
34763
+ wake();
34764
+ };
34765
+ const abort = () => {
34766
+ if (!finished) {
34767
+ controller.abort();
34768
+ if (sessionState === "running")
34769
+ sessionState = "cancelling";
34770
+ }
34771
+ };
34772
+ options.signal?.addEventListener("abort", abort, { once: true });
34773
+ if (options.signal?.aborted)
34774
+ abort();
34775
+ function active() {
34776
+ if (controller.signal.aborted)
34777
+ throw new Stop(deadlineExpired ? "timeout" : "cancelled", deadlineExpired ? "deadline" : "cancelled");
34778
+ if (workflow && Date.now() - started >= workflow.environment.policy.budgets.timeout_ms) {
34779
+ deadlineExpired = true;
34780
+ controller.abort();
34781
+ throw new Stop("timeout", "deadline");
34782
+ }
34783
+ }
34784
+ async function owned(operation, signal, isModel = false) {
34785
+ if (signal.aborted)
34786
+ throw new Stop(deadlineExpired ? "timeout" : "cancelled", deadlineExpired ? "deadline" : "cancelled");
34787
+ const work = Promise.resolve().then(() => {
34788
+ if (signal.aborted)
34789
+ throw new Stop("cancelled", "cancelled");
34790
+ return operation();
34791
+ });
34792
+ pending.add(work);
34793
+ if (isModel)
34794
+ modelPending.add(work);
34795
+ work.then(() => {
34796
+ pending.delete(work);
34797
+ modelPending.delete(work);
34798
+ }, () => {
34799
+ pending.delete(work);
34800
+ modelPending.delete(work);
34801
+ });
34802
+ return new Promise((resolve4, reject) => {
34803
+ const onAbort = () => {
34804
+ signal.removeEventListener("abort", onAbort);
34805
+ reject(new Stop(deadlineExpired ? "timeout" : "cancelled", deadlineExpired ? "deadline" : "cancelled"));
34806
+ };
34807
+ signal.addEventListener("abort", onAbort, { once: true });
34808
+ work.then((value) => {
34809
+ signal.removeEventListener("abort", onAbort);
34810
+ resolve4(value);
34811
+ }, (error2) => {
34812
+ signal.removeEventListener("abort", onAbort);
34813
+ reject(error2);
34814
+ });
34815
+ });
34816
+ }
34817
+ function admit(kind) {
34818
+ active();
34819
+ if (!workflow)
34820
+ throw new Stop("invalid", "invalid_workflow");
34821
+ const budget = workflow.environment.policy.budgets;
34822
+ if (record.budgets.actions >= budget.max_actions)
34823
+ throw new Stop("budget_exceeded", "max_actions");
34824
+ if (kind === "model" && record.budgets.modelRequests >= (budget.max_model_requests ?? budget.max_actions))
34825
+ throw new Stop("budget_exceeded", "max_model_requests");
34826
+ if (kind === "tool" && record.budgets.toolCalls >= (budget.max_tool_calls ?? budget.max_actions))
34827
+ throw new Stop("budget_exceeded", "max_tool_calls");
34828
+ if (budget.max_tokens !== undefined) {
34829
+ if (record.usage.missingRequests)
34830
+ throw new Stop("budget_exceeded", "usage_unavailable");
34831
+ if (record.usage.reported.total >= budget.max_tokens)
34832
+ throw new Stop("budget_exceeded", "max_tokens");
34833
+ }
34834
+ record.budgets.actions++;
34835
+ if (kind === "model")
34836
+ record.budgets.modelRequests++;
34837
+ else
34838
+ record.budgets.toolCalls++;
34839
+ }
34840
+ const ajv3 = new import_ajv5.default({
34841
+ strict: true,
34842
+ allErrors: false,
34843
+ coerceTypes: false,
34844
+ useDefaults: false,
34845
+ removeAdditional: false
34846
+ });
34847
+ async function model(messages, tools2) {
34848
+ if (!workflow)
34849
+ throw new Stop("invalid", "invalid_workflow");
34850
+ const budget = workflow.environment.policy.budgets;
34851
+ const remainingTokens = budget.max_tokens === undefined ? 1e6 : Math.max(1, budget.max_tokens - record.usage.reported.total);
34852
+ const request = {
34853
+ messages: structuredClone(messages),
34854
+ tools: structuredClone(tools2),
34855
+ model: workflow.target.model,
34856
+ generation: {
34857
+ maxTokens: Math.min(workflow.target.generation?.max_tokens ?? 1024, remainingTokens),
34858
+ temperature: workflow.target.generation?.temperature ?? 0
34859
+ },
34860
+ budgets: {
34861
+ timeoutMs: Math.max(1, budget.timeout_ms - (Date.now() - started)),
34862
+ maxToolCalls: 100
34863
+ }
34864
+ };
34865
+ if (!safeBoundary(request) || !agentTurnRequestSchema.safeParse(request).success || !validWorkflowTranscript(messages))
34866
+ throw new Stop("invalid", "transcript_limit");
34867
+ admit("model");
34868
+ const operationId = `model-${record.budgets.modelRequests}`;
34869
+ emit({ type: "model_requested", operationId });
34870
+ let response;
34871
+ let modelCompleted = false;
34872
+ try {
34873
+ try {
34874
+ response = await owned(() => target.turn(request, controller.signal), controller.signal, true);
34875
+ } catch (error2) {
34876
+ if (error2 instanceof Stop)
34877
+ throw error2;
34878
+ throw new Stop("failed", "target_error");
34879
+ }
34880
+ if (!safeBoundary(response)) {
34881
+ record.usage.missingRequests++;
34882
+ throw new Stop("invalid", "invalid_response");
34883
+ }
34884
+ const failure2 = failureSchema.safeParse(response);
34885
+ if (failure2.success) {
34886
+ if (failure2.data.tokens && failure2.data.tokens.total === failure2.data.tokens.prompt + failure2.data.tokens.completion && failure2.data.usageAvailable !== false && (failure2.data.usageAvailable === true || failure2.data.tokens.total > 0)) {
34887
+ measuredRequests++;
34888
+ for (const key of ["prompt", "completion", "total"]) {
34889
+ record.usage.reported[key] += failure2.data.tokens[key];
34890
+ if (phase === "preflight")
34891
+ record.usage.preflight[key] += failure2.data.tokens[key];
34892
+ }
34893
+ } else
34894
+ record.usage.missingRequests++;
34895
+ if (failure2.data.rejectedCall) {
34896
+ const rejection = failure2.data.rejectedCall;
34897
+ const denied = rejection.reason === "undeclared_tool";
34898
+ if (denied)
34899
+ record.policy = "denied";
34900
+ admit("tool");
34901
+ const rejectedOperationId = `tool-${record.budgets.toolCalls}`;
34902
+ const tool = getWorkflowTool(rejection.tool)?.id ?? "unknown";
34903
+ emit({
34904
+ type: "tool_requested",
34905
+ operationId: rejectedOperationId,
34906
+ requestedCallIdHash: rejection.requestedCallIdHash,
34907
+ tool
34908
+ });
34909
+ emit({
34910
+ type: "tool_completed",
34911
+ operationId: rejectedOperationId,
34912
+ requestedCallIdHash: rejection.requestedCallIdHash,
34913
+ tool,
34914
+ status: denied ? "denied" : "invalid"
34915
+ });
34916
+ if (denied)
34917
+ throw new Stop("invalid", "policy_denied");
34918
+ }
34919
+ if (failure2.data.code === "timeout")
34920
+ throw new Stop("timeout", "deadline");
34921
+ if (failure2.data.code === "aborted")
34922
+ throw new Stop("cancelled", "cancelled");
34923
+ throw new Stop(failure2.data.status === "unsupported" ? "unsupported" : failure2.data.status === "invalid" ? "invalid" : "failed", failure2.data.code === "tool_use_unsupported" ? "tool_use_unsupported" : failure2.data.status === "invalid" ? "invalid_response" : "target_error");
34924
+ }
34925
+ const parsed = completedSchema.safeParse(response);
34926
+ if (!parsed.success || parsed.data.tokens.total !== parsed.data.tokens.prompt + parsed.data.tokens.completion) {
34927
+ record.usage.missingRequests++;
34928
+ throw new Stop("invalid", "invalid_response");
34929
+ }
34930
+ const value = parsed.data;
34931
+ const measured = value.usageAvailable !== false && (value.usageAvailable === true || value.tokens.total > 0);
34932
+ if (measured) {
34933
+ measuredRequests++;
34934
+ for (const key of ["prompt", "completion", "total"]) {
34935
+ record.usage.reported[key] += value.tokens[key];
34936
+ if (phase === "preflight")
34937
+ record.usage.preflight[key] += value.tokens[key];
34938
+ }
34939
+ } else
34940
+ record.usage.missingRequests++;
34941
+ record.capability.observedModelHash = hash(value.model);
34942
+ record.capability.observedModel = identity(value.model);
34943
+ const calls = value.message.tool_calls ?? [];
34944
+ if (value.finishReason === "tool_calls" && !calls.length || calls.length && value.finishReason && value.finishReason !== "tool_calls")
34945
+ throw new Stop("invalid", "invalid_response");
34946
+ for (const call of calls) {
34947
+ if (ids.has(call.id))
34948
+ throw new Stop("invalid", "invalid_response");
34949
+ ids.add(call.id);
34950
+ }
34951
+ modelCompleted = true;
34952
+ emit({ type: "model_completed", operationId, status: "completed" });
34953
+ if (budget.max_tokens !== undefined && !measured)
34954
+ throw new Stop("budget_exceeded", "usage_unavailable");
34955
+ if (budget.max_tokens !== undefined && record.usage.reported.total > budget.max_tokens) {
34956
+ record.budgets.tokenOvershoot = record.usage.reported.total - budget.max_tokens;
34957
+ throw new Stop("budget_exceeded", "max_tokens");
34958
+ }
34959
+ active();
34960
+ return value;
34961
+ } finally {
34962
+ if (!modelCompleted)
34963
+ emit({ type: "model_completed", operationId, status: "failed" });
34964
+ }
34965
+ }
34966
+ function originalArguments(call, tools2) {
34967
+ const definition2 = tools2.find((entry) => entry.function.name === call.function.name);
34968
+ if (!definition2) {
34969
+ record.policy = "denied";
34970
+ throw new Stop("invalid", "policy_denied");
34971
+ }
34972
+ let input;
34973
+ try {
34974
+ input = JSON.parse(call.function.arguments);
34975
+ } catch {
34976
+ throw new Stop("invalid", "invalid_response");
34977
+ }
34978
+ if (!isWorkflowState(input) || !ajv3.compile(definition2.function.parameters)(input))
34979
+ throw new Stop("invalid", "invalid_response");
34980
+ return input;
34981
+ }
34982
+ async function probe() {
34983
+ phase = "preflight";
34984
+ const nonce = randomUUID2();
34985
+ const tools2 = [
34986
+ {
34987
+ type: "function",
34988
+ function: {
34989
+ name: "artemis_probe",
34990
+ description: "Echo the exact supplied nonce to verify structured tool protocol.",
34991
+ parameters: {
34992
+ type: "object",
34993
+ properties: { nonce: { const: nonce } },
34994
+ required: ["nonce"],
34995
+ additionalProperties: false
34996
+ }
34997
+ }
34998
+ }
34999
+ ];
35000
+ const messages = [
35001
+ {
35002
+ role: "user",
35003
+ content: `Call artemis_probe once with nonce ${nonce}. After receiving its result, reply with only that nonce.`
35004
+ }
35005
+ ];
35006
+ const first = await model(messages, tools2);
35007
+ const calls = first.message.tool_calls ?? [];
35008
+ if (calls.length !== 1)
35009
+ throw new Stop("unsupported", "preflight_failed");
35010
+ admit("tool");
35011
+ const call = calls[0];
35012
+ originalArguments(call, tools2);
35013
+ const operationId = `tool-${record.budgets.toolCalls}`;
35014
+ const requestedCallIdHash = hash(call.id);
35015
+ emit({ type: "tool_requested", operationId, requestedCallIdHash, tool: "artemis_probe" });
35016
+ messages.push(first.message, {
35017
+ role: "tool",
35018
+ toolCallId: call.id,
35019
+ content: JSON.stringify({ nonce })
35020
+ });
35021
+ emit({
35022
+ type: "tool_completed",
35023
+ operationId,
35024
+ requestedCallIdHash,
35025
+ tool: "artemis_probe",
35026
+ status: "completed"
35027
+ });
35028
+ const second = await model(messages, tools2);
35029
+ if (second.message.tool_calls?.length || second.message.content !== nonce)
35030
+ throw new Stop("unsupported", "preflight_failed");
35031
+ record.capability.preflight = "passed";
35032
+ emit({ type: "preflight_completed", status: "completed" });
35033
+ phase = "execution";
35034
+ }
35035
+ async function executeTool(call, tools2) {
35036
+ if (!workflow || !environment)
35037
+ throw new Stop("failed", "environment_unavailable");
35038
+ admit("tool");
35039
+ const operationId = `tool-${record.budgets.toolCalls}`;
35040
+ const requestedCallIdHash = hash(call.id);
35041
+ const known = getWorkflowTool(call.function.name);
35042
+ emit({
35043
+ type: "tool_requested",
35044
+ operationId,
35045
+ requestedCallIdHash,
35046
+ tool: known?.id ?? "unknown"
35047
+ });
35048
+ let toolCompleted = false;
35049
+ try {
35050
+ const input = originalArguments(call, tools2);
35051
+ if (!workflowToolPermitted(workflow, call.function.name) || !workflowPathAllowed(workflow, call.function.name, input)) {
35052
+ record.policy = "denied";
35053
+ toolCompleted = true;
35054
+ emit({
35055
+ type: "tool_completed",
35056
+ operationId,
35057
+ requestedCallIdHash,
35058
+ tool: known?.id ?? "unknown",
35059
+ status: "denied"
35060
+ });
35061
+ throw new Stop("invalid", "policy_denied");
35062
+ }
35063
+ let value;
35064
+ try {
35065
+ value = await owned(() => environment ? environment.execute({ tool: call.function.name, input: structuredClone(input) }, controller.signal) : Promise.reject(), controller.signal);
35066
+ } catch (error2) {
35067
+ if (error2 instanceof Stop)
35068
+ throw error2;
35069
+ throw new Stop("failed", "tool_failed");
35070
+ }
35071
+ if (!isWorkflowState(value) || !known)
35072
+ throw new Stop("invalid", "invalid_environment");
35073
+ const status = value.status;
35074
+ if (!isWorkflowState(value.evidence) || value.evidence.tool !== known.id || value.evidence.version !== "1" || value.evidence.status !== status || Object.keys(value).some((key) => !(status === "succeeded" ? ["status", "output", "state", "evidence"] : ["status", "code", "evidence"]).includes(key)))
35075
+ throw new Stop("invalid", "invalid_environment");
35076
+ if (status === "succeeded") {
35077
+ if (!isWorkflowState(value.state) || !isWorkflowJson(value.output) || !ajv3.compile(known.outputSchema)(value.output))
35078
+ throw new Stop("invalid", "invalid_environment");
35079
+ state = structuredClone(value.state);
35080
+ transcript.push({
35081
+ role: "tool",
35082
+ toolCallId: call.id,
35083
+ content: JSON.stringify(value.output)
35084
+ });
35085
+ toolCompleted = true;
35086
+ emit({
35087
+ type: "tool_completed",
35088
+ operationId,
35089
+ requestedCallIdHash,
35090
+ tool: known.id,
35091
+ status: "completed"
35092
+ });
35093
+ } else if ((status === "denied" || status === "invalid" || status === "failed") && typeof value.code === "string" && failureCodes.has(value.code)) {
35094
+ if (status === "denied")
35095
+ record.policy = "denied";
35096
+ transcript.push({
35097
+ role: "tool",
35098
+ toolCallId: call.id,
35099
+ content: JSON.stringify({ status, code: value.code })
35100
+ });
35101
+ toolCompleted = true;
35102
+ emit({ type: "tool_completed", operationId, requestedCallIdHash, tool: known.id, status });
35103
+ throw new Stop(status === "denied" ? "invalid" : "failed", status === "denied" ? "policy_denied" : "tool_failed");
35104
+ } else
35105
+ throw new Stop("invalid", "invalid_environment");
35106
+ active();
35107
+ } finally {
35108
+ if (!toolCompleted)
35109
+ emit({
35110
+ type: "tool_completed",
35111
+ operationId,
35112
+ requestedCallIdHash,
35113
+ tool: known?.id ?? "unknown",
35114
+ status: record.policy === "denied" ? "denied" : "failed"
35115
+ });
35116
+ }
35117
+ }
35118
+ async function run() {
35119
+ sessionState = controller.signal.aborted ? "cancelling" : "running";
35120
+ started = Date.now();
35121
+ const timeout = workflow?.environment.policy.budgets.timeout_ms ?? 1;
35122
+ const timer = setTimeout(() => {
35123
+ deadlineExpired = true;
35124
+ abort();
35125
+ }, timeout);
35126
+ const cleanupMs = options.cleanupTimeoutMs ?? (workflow?.environment.type === "sandbox" ? 6000 : 1000);
35127
+ try {
35128
+ if (!workflow)
35129
+ throw new Stop("invalid", "invalid_workflow");
35130
+ if (!Number.isInteger(cleanupMs) || cleanupMs < 1 || cleanupMs > 1e4 || !target || typeof target.turn !== "function" || typeof target.capabilities !== "function")
35131
+ throw new Stop("invalid", "invalid_options");
35132
+ active();
35133
+ emit({ type: "started" });
35134
+ const initialState = options.preflightOnly ? {} : await owned(() => resolveWorkflowInitialState(workflow, options.fixtureRoot), controller.signal).catch((error2) => {
35135
+ if (error2 instanceof Stop)
35136
+ throw error2;
35137
+ throw new Stop("invalid", "invalid_fixture");
35138
+ });
35139
+ active();
35140
+ const advertised = await owned(() => target.capabilities({ timeoutMs: Math.max(1, timeout - (Date.now() - started)) }, controller.signal), controller.signal);
35141
+ if (!safeBoundary(advertised))
35142
+ throw new Stop("invalid", "invalid_response");
35143
+ const capability = capabilitySchema.safeParse(advertised);
35144
+ if (!capability.success)
35145
+ throw new Stop("unsupported", "target_unavailable");
35146
+ record.capability.advertised = capability.data.toolUse;
35147
+ record.capability.transportCancellation = capability.data.transportCancellation;
35148
+ if (!capability.data.toolUse)
35149
+ throw new Stop("unsupported", "tool_use_unsupported");
35150
+ if (options.preflight || options.preflightOnly)
35151
+ await probe();
35152
+ if (!options.preflightOnly) {
35153
+ active();
35154
+ const factory2 = options.environmentFactory ?? (workflow.environment.type === "simulated" ? createSimulatedWorkflowEnvironment : createDockerWorkflowEnvironment);
35155
+ environment = await owned(async () => {
35156
+ const created = await factory2({
35157
+ workflow: structuredClone(workflow),
35158
+ initialState: structuredClone(initialState),
35159
+ signal: controller.signal
35160
+ });
35161
+ environment = created;
35162
+ if (finished && created && typeof created.close === "function") {
35163
+ const lateController = new AbortController;
35164
+ const lateTimer = setTimeout(() => lateController.abort(), Math.min(1000, cleanupMs));
35165
+ try {
35166
+ await owned(() => created.close(lateController.signal), lateController.signal);
35167
+ } catch {} finally {
35168
+ clearTimeout(lateTimer);
35169
+ }
35170
+ }
35171
+ return created;
35172
+ }, controller.signal);
35173
+ if (!environment || environment.type !== workflow.environment.type || typeof environment.execute !== "function" || typeof environment.snapshot !== "function" || typeof environment.close !== "function" || environment.capabilities?.network !== "denied" || environment.capabilities.commands !== "denied" || environment.capabilities.externalSideEffects !== "denied" || environment.capabilities.isolation !== (workflow.environment.type === "simulated" ? "memory" : "container"))
35174
+ throw new Stop("invalid", "invalid_environment");
35175
+ state = structuredClone(initialState);
35176
+ transcript = [{ role: "system", content: workflow.workflow.system_instructions }];
35177
+ const tools2 = workflow.tools.map((id) => {
35178
+ const descriptor2 = getWorkflowTool(id);
35179
+ if (!descriptor2)
35180
+ throw new Stop("invalid", "invalid_workflow");
35181
+ return {
35182
+ type: "function",
35183
+ function: {
35184
+ name: id,
35185
+ description: descriptor2.description,
35186
+ parameters: descriptor2.inputSchema
35187
+ }
35188
+ };
35189
+ });
35190
+ for (const turn of workflow.workflow.turns) {
35191
+ transcript.push(structuredClone(turn));
35192
+ while (true) {
35193
+ const answer = await model(transcript, tools2);
35194
+ transcript.push(answer.message);
35195
+ const calls = answer.message.tool_calls ?? [];
35196
+ if (!calls.length)
35197
+ break;
35198
+ for (const call of calls)
35199
+ await executeTool(call, tools2);
35200
+ }
35201
+ }
35202
+ }
35203
+ record.execution = "completed";
35204
+ record.reason = "finished";
35205
+ } catch (error2) {
35206
+ if (error2 instanceof WorkflowEnvironmentInitializationError) {
35207
+ const detail = error2.cleanup;
35208
+ const checked = safeBoundary(detail) ? cleanupSchema.extend({ pendingOperations: exports_external.number().int().nonnegative().safe() }).safeParse(detail) : null;
35209
+ record.cleanup = checked?.success ? checked.data : { status: "unresolved", artifacts: "unknown", pendingOperations: 1 };
35210
+ }
35211
+ const stop = error2 instanceof WorkflowEnvironmentInitializationError ? new Stop("failed", "environment_unavailable") : error2 instanceof Stop ? error2 : new Stop("failed", "target_error");
35212
+ record.execution = stop.execution;
35213
+ record.reason = stop.reason;
35214
+ } finally {
35215
+ clearTimeout(timer);
35216
+ controller.abort();
35217
+ const cleanup = new AbortController;
35218
+ const drainController = new AbortController;
35219
+ const drainBudget = Math.max(1, Math.floor((Number.isInteger(cleanupMs) && cleanupMs >= 1 && cleanupMs <= 1e4 ? cleanupMs : 1000) / 3));
35220
+ const drainTimer = setTimeout(() => drainController.abort(), drainBudget);
35221
+ let adapterPending = 0;
35222
+ try {
35223
+ const drain = target?.drain;
35224
+ if (typeof drain === "function") {
35225
+ const value = await owned(() => drain.call(target, { timeoutMs: drainBudget }), drainController.signal);
35226
+ if (value && Number.isSafeInteger(value.pendingOperations) && value.pendingOperations >= 0)
35227
+ adapterPending = value.pendingOperations;
35228
+ else
35229
+ adapterPending = 1;
35230
+ }
35231
+ const callbacks = [...pending];
35232
+ if (callbacks.length)
35233
+ await owned(() => Promise.allSettled(callbacks), drainController.signal);
35234
+ } catch {
35235
+ adapterPending = typeof target?.drain === "function" ? Math.max(1, adapterPending) : adapterPending;
35236
+ }
35237
+ clearTimeout(drainTimer);
35238
+ record.usage.inFlightUnknown = modelPending.size > 0 || adapterPending > 0;
35239
+ const pendingBeforeClose = pending.size;
35240
+ if (environment && typeof environment.close === "function") {
35241
+ const snapshotController = new AbortController;
35242
+ const snapshotTimer = setTimeout(() => snapshotController.abort(), drainBudget);
35243
+ if (!pendingBeforeClose && typeof environment.snapshot === "function" && !cleanup.signal.aborted) {
35244
+ try {
35245
+ const snapshot = await owned(() => environment.snapshot(snapshotController.signal), snapshotController.signal);
35246
+ if (isWorkflowState(snapshot))
35247
+ state = structuredClone(snapshot);
35248
+ else
35249
+ state = null;
35250
+ } catch {
35251
+ state = null;
35252
+ }
35253
+ } else
35254
+ state = null;
35255
+ clearTimeout(snapshotTimer);
35256
+ const closeTimer = setTimeout(() => cleanup.abort(), drainBudget);
35257
+ try {
35258
+ const result = await owned(() => environment.close(cleanup.signal), cleanup.signal);
35259
+ const checked = safeBoundary(result) ? cleanupSchema.safeParse(result) : null;
35260
+ if (checked?.success)
35261
+ record.cleanup = { ...checked.data, pendingOperations: 0 };
35262
+ else
35263
+ record.cleanup = { status: "unresolved", artifacts: "unknown", pendingOperations: 0 };
35264
+ } catch {
35265
+ record.cleanup = { status: "unresolved", artifacts: "unknown", pendingOperations: 0 };
35266
+ } finally {
35267
+ clearTimeout(closeTimer);
35268
+ }
35269
+ }
35270
+ record.cleanup.pendingOperations = Math.max(record.cleanup.pendingOperations, pending.size, adapterPending);
35271
+ if (record.cleanup.pendingOperations || pendingBeforeClose || cleanup.signal.aborted) {
35272
+ record.cleanup.status = "unresolved";
35273
+ record.cleanup.artifacts = "unknown";
35274
+ }
35275
+ if (record.cleanup.status === "unresolved")
35276
+ state = null;
35277
+ options.signal?.removeEventListener("abort", abort);
35278
+ record.usage.missingRequests = record.budgets.modelRequests - measuredRequests;
35279
+ const tokenLimit = workflow?.environment.policy.budgets.max_tokens;
35280
+ record.budgets.tokenOvershoot = tokenLimit === undefined ? 0 : Math.max(0, record.usage.reported.total - tokenLimit);
35281
+ record.usage.status = measuredRequests ? record.usage.missingRequests || record.usage.inFlightUnknown ? "partial" : "reported" : "unavailable";
35282
+ if (state) {
35283
+ const files = isWorkflowState(state.files) ? Object.entries(state.files).filter((entry) => typeof entry[1] === "string").sort(([a2], [b2]) => a2.localeCompare(b2)) : [];
35284
+ record.artifacts = {
35285
+ state: "available",
35286
+ stateSha256: hash(JSON.stringify(state)),
35287
+ files: files.slice(0, 100).map(([path, content]) => ({
35288
+ pathSha256: hash(path),
35289
+ contentSha256: hash(content),
35290
+ bytes: Buffer.byteLength(content)
35291
+ })),
35292
+ omittedFiles: Math.max(0, files.length - 100)
35293
+ };
35294
+ }
35295
+ record.budgets.elapsedMs = Math.max(0, Date.now() - started);
35296
+ emit({ type: "finished", status: record.execution === "completed" ? "completed" : "failed" });
35297
+ finished = true;
35298
+ sessionState = "completed";
35299
+ for (const wake of listeners)
35300
+ wake();
35301
+ }
35302
+ return {
35303
+ record: structuredClone(record),
35304
+ state: state ? structuredClone(state) : null,
35305
+ transcript: structuredClone(transcript)
35306
+ };
35307
+ }
35308
+ return {
35309
+ get state() {
35310
+ return sessionState;
35311
+ },
35312
+ run() {
35313
+ if (!promise) {
35314
+ sessionState = controller.signal.aborted ? "cancelling" : "running";
35315
+ promise = Promise.resolve().then(run);
35316
+ }
35317
+ return promise;
35318
+ },
35319
+ cancel: abort,
35320
+ async* events() {
35321
+ let index = 0;
35322
+ while (true) {
35323
+ while (index < retained.length)
35324
+ yield structuredClone(retained[index++]);
35325
+ if (finished)
35326
+ return;
35327
+ await new Promise((resolve4) => {
35328
+ const wake = () => {
35329
+ listeners.delete(wake);
35330
+ resolve4();
35331
+ };
35332
+ listeners.add(wake);
35333
+ });
35334
+ }
35335
+ }
35336
+ };
35337
+ }
35338
+ function runAgentWorkflow(options) {
35339
+ return createAgentWorkflowSession(options).run();
35340
+ }
35341
+ // src/validator/validator.ts
35342
+ var import_yaml4 = __toESM(require_dist(), 1);
34011
35343
  import { readFileSync } from "node:fs";
34012
35344
  class ScenarioValidator {
34013
35345
  _options;
@@ -34035,12 +35367,12 @@ class ScenarioValidator {
34035
35367
  }
34036
35368
  let parsed;
34037
35369
  try {
34038
- parsed = import_yaml3.default.parse(content, {
35370
+ parsed = import_yaml4.default.parse(content, {
34039
35371
  prettyErrors: true,
34040
35372
  strict: true
34041
35373
  });
34042
35374
  } catch (err) {
34043
- if (err instanceof import_yaml3.default.YAMLError) {
35375
+ if (err instanceof import_yaml4.default.YAMLError) {
34044
35376
  const linePos = err.linePos?.[0];
34045
35377
  errors3.push({
34046
35378
  line: linePos?.line || 1,
@@ -34253,14 +35585,19 @@ class ScenarioValidator {
34253
35585
  }
34254
35586
  export {
34255
35587
  wrapError,
35588
+ workflowToolPermitted,
35589
+ workflowPathAllowed,
34256
35590
  validateToolArguments,
34257
35591
  validateScenario,
34258
35592
  validateAgentWorkflow,
35593
+ validWorkflowTranscript,
34259
35594
  substituteVariables,
34260
35595
  substituteString,
34261
35596
  scoreAgentOutcome,
34262
35597
  runScenarios,
34263
35598
  runScenario,
35599
+ runAgentWorkflow,
35600
+ resolveWorkflowInitialState,
34264
35601
  resolveScenarioPaths,
34265
35602
  resolvePatterns,
34266
35603
  registerEvaluator,
@@ -34279,6 +35616,7 @@ export {
34279
35616
  listKnownModels,
34280
35617
  listEvaluators,
34281
35618
  listAdapters,
35619
+ isWorkflowState,
34282
35620
  isWorkflowRelativePath,
34283
35621
  isWorkflowJson,
34284
35622
  isStressManifest,
@@ -34302,19 +35640,26 @@ export {
34302
35640
  createWorkloadIdentity,
34303
35641
  createStorageFromEnv,
34304
35642
  createStorageAdapter,
35643
+ createSimulatedWorkflowEnvironment,
34305
35644
  createRunManifest,
34306
35645
  createRedactionOptions,
34307
35646
  createNoOpRedactor,
34308
35647
  createModelClientTarget,
34309
35648
  createExecutionProvenance,
35649
+ createDockerWorkflowEnvironmentFactory,
35650
+ createDockerWorkflowEnvironment,
34310
35651
  createDefaultRedactor,
35652
+ createAgentWorkflowSession,
34311
35653
  createAdapter,
34312
35654
  assessComparisonEligibility,
34313
35655
  assertRunManifestIntegrity,
35656
+ agentTurnRequestSchema,
34314
35657
  adapterRegistry,
34315
35658
  actionBudgetExceeded,
34316
35659
  WorkflowPolicySchema,
35660
+ WorkflowEnvironmentInitializationError,
34317
35661
  WORKFLOW_TOOL_IDS,
35662
+ WORKFLOW_SANDBOX_IMAGE,
34318
35663
  VariablesSchema,
34319
35664
  ToolTraceEvaluator,
34320
35665
  TestCaseSchema,