@telnyx/agent-harness 0.1.0-beta.0 → 0.1.0-beta.2

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.
package/dist/harness.js CHANGED
@@ -1,15 +1,26 @@
1
1
  import { GatewayError } from "@ai-sdk/gateway";
2
- import { AISDKError, APICallError, RetryError, asSchema, generateText, jsonSchema, stepCountIs, wrapLanguageModel, } from "ai";
2
+ import { AISDKError, APICallError, RetryError, asSchema, generateText, streamText, jsonSchema, stepCountIs, wrapLanguageModel, } from "ai";
3
3
  import { z } from "zod/v4";
4
+ import { traceActive, traceBind, voiceTraceContext, traceAttempt, traceStage } from "@telnyx/edge-runtime/internal";
5
+ import { traceModel, traceToolExecution } from "./voice-trace.js";
4
6
  import { isToolOutputSerializationFailure } from "./steps.js";
7
+ import { inspectHarnessRun } from "./diagnostics.js";
5
8
  import { HarnessApprovalError, createHarnessApprovalExecutionLedger, createHarnessApprovalMessageJournal, } from "./approvals.js";
6
9
  import { createHarnessRunExecutionLedger, HarnessApprovalPause, } from "./durable.js";
7
10
  import { runMiddleware } from "./lifecycle.js";
8
11
  import { withHarnessToolContext } from "./tool-context.js";
9
12
  import { resolveHarnessRuntimeConfig, } from "./runtime-config.js";
13
+ import { HarnessSpeculationUnsupportedError, serializeTurn, speculativeTurn, } from "./speculative.js";
14
+ export { HarnessSpeculationConflictError, HarnessSpeculationUnsupportedError, } from "./speculative.js";
10
15
  const maxStepsSchema = z.number().int().positive();
11
16
  const TOOL_OUTPUT_PREFIX = "\u001e@telnyx/agent-harness:tool-output:v1:";
12
17
  const DURABLE_TOOL_STEP_VERSION = "tool-execution-v1";
18
+ function assertSpeculativeSdkHooks() {
19
+ if (globalThis.AI_SDK_TELEMETRY_INTEGRATIONS?.length
20
+ || typeof globalThis.AI_SDK_LOG_WARNINGS === "function") {
21
+ throw new HarnessSpeculationUnsupportedError("global AI SDK lifecycle integrations or custom warning callbacks");
22
+ }
23
+ }
13
24
  /** Raised after the final allowed model step still requests executable tools. */
14
25
  export class HarnessStepLimitError extends Error {
15
26
  maxSteps;
@@ -66,6 +77,45 @@ export class HarnessObserverError extends Error {
66
77
  this.name = "HarnessObserverError";
67
78
  }
68
79
  }
80
+ async function cancelTurn(onCancel, cancellation = new HarnessCancellationError()) {
81
+ try {
82
+ await onCancel?.(cancellation);
83
+ }
84
+ catch (observer) {
85
+ throw new HarnessObserverError(cancellation, observer, "cancel");
86
+ }
87
+ throw cancellation;
88
+ }
89
+ function captureSpeculativeOptions(options) {
90
+ // Inspect descriptors, not values: spreading invokes getters. A detached
91
+ // null-prototype snapshot cannot inherit hooks installed while queued.
92
+ const supported = new Set(["signal", "onStepCommitted", "onOutput"]);
93
+ const unsupported = new Set(["steps", "callbacks", "onCancel", "onError"]);
94
+ if (options === null || typeof options !== "object"
95
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(options))) {
96
+ throw new HarnessSpeculationUnsupportedError("non-plain turn options");
97
+ }
98
+ const captured = Object.create(null);
99
+ for (const key of Reflect.ownKeys(options)) {
100
+ const descriptor = Object.getOwnPropertyDescriptor(options, key);
101
+ if (key === voiceTraceContext && descriptor.enumerable && "value" in descriptor) {
102
+ Object.defineProperty(captured, key, descriptor);
103
+ continue;
104
+ }
105
+ if (typeof key !== "string" || !descriptor.enumerable || !("value" in descriptor)
106
+ || (!supported.has(key) && !unsupported.has(key))
107
+ || (unsupported.has(key) && descriptor.value !== undefined)) {
108
+ throw new HarnessSpeculationUnsupportedError("accessors, inherited options, or pre-model lifecycle hooks");
109
+ }
110
+ if (supported.has(key)) {
111
+ if (key !== "signal" && descriptor.value !== undefined && typeof descriptor.value !== "function") {
112
+ throw new HarnessSpeculationUnsupportedError("non-function observers");
113
+ }
114
+ Object.defineProperty(captured, key, descriptor);
115
+ }
116
+ }
117
+ return Object.freeze(captured);
118
+ }
69
119
  function historyMessages(stored) {
70
120
  return stored.map((message) => {
71
121
  if (message.role === "assistant") {
@@ -652,9 +702,11 @@ function validateTools(tools, authorization) {
652
702
  }
653
703
  /** Construct a portable AI SDK v6 turn engine over an explicit ports object. */
654
704
  export function createHarness(spec) {
705
+ const activation = spec.ports.activation;
706
+ const diagnostics = traceBind(spec.diagnostics, activation);
655
707
  const messagePort = spec.ports.messages;
656
- const appendMessage = messagePort.append.bind(messagePort);
657
- const appendMessages = messagePort.appendMany.bind(messagePort);
708
+ const appendMessagePort = messagePort.append.bind(messagePort);
709
+ const appendMessagesPort = messagePort.appendMany.bind(messagePort);
658
710
  const allMessages = messagePort.all.bind(messagePort);
659
711
  const getState = spec.ports.state.get.bind(spec.ports.state);
660
712
  const model = spec.model;
@@ -697,22 +749,25 @@ export function createHarness(spec) {
697
749
  let lastValidRuntimeConfig = resolveHarnessRuntimeConfig(staticRuntimeConfig, undefined).config;
698
750
  let runtimeConfigReadSequence = 0;
699
751
  let lastPublishedRuntimeConfigReadSequence = 0;
700
- const turn = async (input, options = {}) => {
752
+ const executeTurn = async (input, options = {}, speculation) => {
753
+ const trace = options[voiceTraceContext]?.scope === diagnostics ? options[voiceTraceContext] : traceAttempt(diagnostics, 0, "confirmed");
754
+ const committedEffect = (work) => speculation ? speculation.write(work) : work();
755
+ const appendMessage = (message) => speculation
756
+ ? speculation.write(() => appendMessagePort(message)) : appendMessagePort(message);
757
+ const appendMessages = (batch) => speculation
758
+ ? speculation.write(() => appendMessagesPort(batch)) : appendMessagesPort(batch);
701
759
  if (options.signal?.aborted) {
702
- const cancellation = new HarnessCancellationError();
703
- try {
704
- await options.onCancel?.(cancellation);
705
- }
706
- catch (observer) {
707
- throw new HarnessObserverError(cancellation, observer, "cancel");
708
- }
709
- throw cancellation;
760
+ return cancelTurn(options.onCancel);
710
761
  }
711
762
  try {
712
763
  const runtimeConfigReadSequenceForTurn = ++runtimeConfigReadSequence;
713
764
  const state = await getState();
765
+ speculation?.assertLive();
714
766
  const resolution = resolveHarnessRuntimeConfig(staticRuntimeConfig, state.__telnyx_agent_harness, lastValidRuntimeConfig);
715
767
  const runtimeConfig = resolution.config;
768
+ if (speculation && runtimeConfig.output !== undefined) {
769
+ throw new HarnessSpeculationUnsupportedError("structured output");
770
+ }
716
771
  if (typeof runtimeConfig.model === "string" && lifecycleMiddleware.modelStep.length > 0) {
717
772
  throw new TypeError("Harness model-step middleware requires an executable LanguageModel");
718
773
  }
@@ -731,7 +786,11 @@ export function createHarness(spec) {
731
786
  });
732
787
  return await runMiddleware(turnContext, lifecycleMiddleware.turn, async () => {
733
788
  await toolSnapshot.preflight(Object.keys(runtimeConfig.tools ?? {}));
789
+ speculation?.assertLive();
734
790
  const priorMessages = await allMessages();
791
+ speculation?.assertLive();
792
+ if (speculation)
793
+ assertSpeculativeSdkHooks();
735
794
  const inputSteps = options.steps;
736
795
  const inputJournal = inputSteps === undefined
737
796
  ? undefined
@@ -775,10 +834,29 @@ export function createHarness(spec) {
775
834
  const replaysJournaledInput = journaledInput !== undefined;
776
835
  let journalThrough = replaysJournaledInput
777
836
  ? priorMessages.at(-1).seq
778
- : await appendMessage({ role: "user", content: input });
779
- const storedMessages = !replaysJournaledInput
780
- ? await allMessages()
781
- : priorMessages;
837
+ : speculation ? (priorMessages.at(-1)?.seq ?? 0) : await appendMessage({ role: "user", content: input });
838
+ const storedMessages = speculation
839
+ ? [...priorMessages, { role: "user", content: input, seq: journalThrough + 1, at: new Date(spec.ports.clock.now()) }]
840
+ : !replaysJournaledInput
841
+ ? await allMessages()
842
+ : priorMessages;
843
+ // The append-only port allocates the confirmed input's actual sequence.
844
+ // No speculative reservation can conflict with unrelated message writers.
845
+ let publication;
846
+ let confirmedForModel;
847
+ const confirmedMessages = (batch) => confirmedForModel === undefined ? batch : batch.map((message, index) => index === priorMessages.length ? { role: "user", content: confirmedForModel } : message);
848
+ const publishInput = () => {
849
+ if (!speculation)
850
+ return Promise.resolve();
851
+ publication ??= (async () => {
852
+ const confirmed = await speculation.confirmed();
853
+ speculation.assertLive();
854
+ confirmedForModel = confirmed;
855
+ journalThrough = await appendMessage({ role: "user", content: confirmed });
856
+ speculation.assertLive();
857
+ })();
858
+ return publication;
859
+ };
782
860
  const restoredApprovals = options.steps === undefined
783
861
  ? []
784
862
  : await approvalMessages.restoreRun(options.steps.runId);
@@ -825,9 +903,18 @@ export function createHarness(spec) {
825
903
  let modelStepNumber = 0;
826
904
  const limitReached = stepCountIs(runtimeConfig.limits.steps);
827
905
  const assertBoundary = () => {
906
+ speculation?.assertLive();
828
907
  if (boundaryFailed)
829
908
  throw boundaryError;
830
909
  };
910
+ if (speculation)
911
+ void publishInput().catch((error) => {
912
+ if (!boundaryFailed) {
913
+ boundaryFailed = true;
914
+ boundaryError = error;
915
+ }
916
+ speculation.fail(error);
917
+ });
831
918
  const modelForTurn = lifecycleMiddleware.modelStep.length === 0 || typeof runtimeConfig.model === "string"
832
919
  ? runtimeConfig.model
833
920
  : wrapLanguageModel({
@@ -847,7 +934,21 @@ export function createHarness(spec) {
847
934
  toolName,
848
935
  Object.freeze({
849
936
  ...candidate,
937
+ ...(speculation ? {
938
+ inputSchema: jsonSchema(async () => await asSchema(candidate.inputSchema).jsonSchema, {
939
+ validate: async (value) => {
940
+ await publishInput();
941
+ assertBoundary();
942
+ const schema = asSchema(candidate.inputSchema);
943
+ return schema.validate
944
+ ? await committedEffect(async () => await schema.validate(value))
945
+ : { success: true, value };
946
+ },
947
+ }),
948
+ } : {}),
850
949
  async execute(...args) {
950
+ await publishInput();
951
+ assertBoundary();
851
952
  const toolCallOptions = args[1];
852
953
  const toolCallId = typeof toolCallOptions?.toolCallId === "string"
853
954
  ? toolCallOptions.toolCallId
@@ -862,13 +963,14 @@ export function createHarness(spec) {
862
963
  let coreCompleted = false;
863
964
  let replayingCompletedApproval = false;
864
965
  try {
865
- const output = await runMiddleware(Object.freeze({
966
+ const output = await committedEffect(() => runMiddleware(Object.freeze({
866
967
  phase: "tool",
867
968
  config: runtimeConfig,
868
969
  toolName,
869
970
  toolCallId,
870
971
  signal: options.signal,
871
972
  }), lifecycleMiddleware.tool, async () => {
973
+ assertBoundary();
872
974
  const stepName = `tool:${toolName}:${durableToolCallOrdinal}`;
873
975
  const stepVersion = spec.durability?.toolStepVersion ?? DURABLE_TOOL_STEP_VERSION;
874
976
  const restoringCompletedApproval = options.steps !== undefined
@@ -881,13 +983,21 @@ export function createHarness(spec) {
881
983
  ? undefined
882
984
  : await approvals.reauthorizeEffect(options.steps.runId, toolCallId, toolName, args[0]);
883
985
  const approvalId = authorizedEffect?.id;
884
- const effectArgs = authorizedEffect === undefined
986
+ let effectArgs = authorizedEffect === undefined
885
987
  ? args
886
988
  : [authorizedEffect.input, ...args.slice(1)];
989
+ if (speculation) {
990
+ const callOptions = objectRecord(effectArgs[1]);
991
+ if (Array.isArray(callOptions?.messages))
992
+ effectArgs = [effectArgs[0], {
993
+ ...callOptions, messages: confirmedMessages(callOptions.messages),
994
+ }, ...effectArgs.slice(2)];
995
+ }
996
+ assertBoundary();
887
997
  coreStarted = true;
888
998
  const result = options.steps === undefined
889
- ? cloneToolOutput(toolName, await execute.apply(this, args))
890
- : await options.steps.step(stepName, async () => await withHarnessToolContext(Object.freeze({ runId: options.steps.runId, toolName, ordinal: durableToolCallOrdinal }), async () => await execute.apply(this, effectArgs)), {
999
+ ? cloneToolOutput(toolName, await committedEffect(async () => { traceToolExecution(trace, effectArgs[1]?.experimental_context); return await execute.apply(this, effectArgs); }))
1000
+ : await options.steps.step(stepName, async () => await withHarnessToolContext(Object.freeze({ runId: options.steps.runId, toolName, ordinal: durableToolCallOrdinal }), async () => { traceToolExecution(trace, effectArgs[1]?.experimental_context); return await execute.apply(this, effectArgs); }), {
891
1001
  external: true,
892
1002
  beforeEffect: () => approvals.consumeAuthorizedEffect(options.steps.runId, approvalId),
893
1003
  input: args[0],
@@ -895,7 +1005,7 @@ export function createHarness(spec) {
895
1005
  });
896
1006
  coreCompleted = true;
897
1007
  return result;
898
- });
1008
+ }));
899
1009
  const restoredCall = toolCallId === undefined ? undefined : restoredApprovalCalls.get(toolCallId);
900
1010
  if (restoredCall !== undefined) {
901
1011
  const approvedOutput = typeof output === "string"
@@ -937,8 +1047,8 @@ export function createHarness(spec) {
937
1047
  durableSteps.nextToolCallOrdinal(event.toolCall.toolName, event.toolCall.toolCallId);
938
1048
  await options.callbacks?.onToolCallStart?.(event);
939
1049
  };
940
- const generation = generateText({
941
- model: modelForTurn,
1050
+ const generationOptions = {
1051
+ model: traceModel(modelForTurn, trace),
942
1052
  tools: toolsForTurn,
943
1053
  ...(runtimeConfig.output === undefined ? {} : { output: runtimeConfig.output }),
944
1054
  abortSignal: options.signal,
@@ -948,9 +1058,15 @@ export function createHarness(spec) {
948
1058
  experimental_onStepStart: options.callbacks?.onStepStart,
949
1059
  experimental_onToolCallStart: onToolCallStart,
950
1060
  experimental_onToolCallFinish: options.callbacks?.onToolCallFinish,
951
- prepareStep() {
1061
+ prepareStep({ messages: stepMessages }) {
952
1062
  assertBoundary();
953
- return undefined;
1063
+ const confirmed = speculation && confirmedForModel !== undefined ? { messages: confirmedMessages(stepMessages) } : undefined;
1064
+ if (!traceActive(trace))
1065
+ return confirmed;
1066
+ // An empty per-step handoff carries no diagnostic metadata into results.
1067
+ // The source model and executor privately share its object identity.
1068
+ const handoff = Object.freeze({});
1069
+ return { ...confirmed, model: traceModel(modelForTurn, trace, handoff), experimental_context: handoff };
954
1070
  },
955
1071
  async stopWhen(context) {
956
1072
  assertBoundary();
@@ -961,6 +1077,7 @@ export function createHarness(spec) {
961
1077
  },
962
1078
  async onStepFinish(step) {
963
1079
  try {
1080
+ await publishInput();
964
1081
  assertBoundary();
965
1082
  const approvalRequests = step.content.filter((part) => part.type === "tool-approval-request");
966
1083
  const pendingRequests = options.steps === undefined ? [] : await Promise.all(approvalRequests.map(async (request) => replayedCompletedApprovalToolCallIds.has(request.toolCall.toolCallId)
@@ -1042,7 +1159,12 @@ export function createHarness(spec) {
1042
1159
  projectedToolCallIds.add(call.toolCallId);
1043
1160
  for (const call of restoredCalls)
1044
1161
  projectedRestoredToolCallIds.add(call.toolCallId);
1045
- await options.onStepCommitted?.(observedStep(step, journalThrough));
1162
+ assertBoundary();
1163
+ if (options.onStepCommitted) {
1164
+ const snapshot = observedStep(step, journalThrough);
1165
+ await committedEffect(async () => { await options.onStepCommitted(snapshot); });
1166
+ }
1167
+ assertBoundary();
1046
1168
  await options.callbacks?.onStepFinish?.(step);
1047
1169
  }
1048
1170
  catch (error) {
@@ -1054,7 +1176,40 @@ export function createHarness(spec) {
1054
1176
  }
1055
1177
  },
1056
1178
  onFinish: options.callbacks?.onFinish,
1057
- });
1179
+ };
1180
+ const generation = speculation ? (async () => {
1181
+ let streamFailure;
1182
+ let streamFailed = false;
1183
+ const onStreamError = (error) => {
1184
+ streamFailed = true;
1185
+ streamFailure = boundaryFailed ? boundaryError : isProviderFailure(error)
1186
+ ? new HarnessProviderError(providerDiagnostics(runtimeConfig.model, error)) : error;
1187
+ speculation.fail(streamFailure);
1188
+ };
1189
+ const streamed = streamText({
1190
+ ...generationOptions,
1191
+ onError: ({ error }) => onStreamError(error),
1192
+ onChunk: ({ chunk }) => {
1193
+ assertBoundary();
1194
+ const part = objectRecord(chunk);
1195
+ if (part?.type === "text-delta" && typeof part.text === "string") {
1196
+ traceStage(trace, "text_forwarded");
1197
+ speculation.text(part.text);
1198
+ }
1199
+ },
1200
+ });
1201
+ await streamed.consumeStream({ onError: onStreamError });
1202
+ assertBoundary();
1203
+ if (streamFailed)
1204
+ throw streamFailure;
1205
+ return {
1206
+ text: await streamed.text,
1207
+ steps: await streamed.steps,
1208
+ finishReason: await streamed.finishReason,
1209
+ totalUsage: await streamed.totalUsage,
1210
+ output: undefined,
1211
+ };
1212
+ })() : generateText(generationOptions);
1058
1213
  let result;
1059
1214
  try {
1060
1215
  result = await generation;
@@ -1075,6 +1230,8 @@ export function createHarness(spec) {
1075
1230
  name: call.toolName,
1076
1231
  })));
1077
1232
  }
1233
+ if (!speculation)
1234
+ traceStage(trace, "text_forwarded");
1078
1235
  const output = Object.freeze({
1079
1236
  text: result.text,
1080
1237
  stepCount: result.steps.length,
@@ -1089,7 +1246,9 @@ export function createHarness(spec) {
1089
1246
  ? {}
1090
1247
  : { output: result.output }),
1091
1248
  });
1092
- await options.onOutput?.(output);
1249
+ assertBoundary();
1250
+ if (options.onOutput)
1251
+ await committedEffect(async () => { await options.onOutput(output); });
1093
1252
  return output;
1094
1253
  });
1095
1254
  }
@@ -1102,13 +1261,7 @@ export function createHarness(spec) {
1102
1261
  const classified = error instanceof HarnessCancellationError
1103
1262
  ? error
1104
1263
  : new HarnessCancellationError();
1105
- try {
1106
- await options.onCancel?.(classified);
1107
- }
1108
- catch (observer) {
1109
- throw new HarnessObserverError(classified, observer, "cancel");
1110
- }
1111
- throw classified;
1264
+ return cancelTurn(options.onCancel, classified);
1112
1265
  }
1113
1266
  try {
1114
1267
  await options.onError?.(error);
@@ -1119,6 +1272,23 @@ export function createHarness(spec) {
1119
1272
  throw error;
1120
1273
  }
1121
1274
  };
1275
+ const turn = (input, options = {}) => serializeTurn(activation, () => executeTurn(input, options), false, options.signal, () => cancelTurn(options.onCancel));
1276
+ const begin = (input, options = {}) => {
1277
+ const capturedOptions = captureSpeculativeOptions(options);
1278
+ assertSpeculativeSdkHooks();
1279
+ // A captured catalog can select output from async runtime state. Refuse
1280
+ // before returning a handle so voice can safely fall back after confirmation.
1281
+ if (lifecycleMiddleware.turn.length || lifecycleMiddleware.modelStep.length
1282
+ || staticRuntimeConfig.output !== undefined || Object.keys(outputs).length > 0) {
1283
+ throw new HarnessSpeculationUnsupportedError("durable step contexts, pre-model lifecycle hooks, or structured output");
1284
+ }
1285
+ return speculativeTurn(activation, capturedOptions.signal, () => new HarnessCancellationError(), (control) => executeTurn(input, Object.freeze(Object.assign(Object.create(null), {
1286
+ signal: control.signal,
1287
+ onStepCommitted: capturedOptions.onStepCommitted,
1288
+ onOutput: capturedOptions.onOutput,
1289
+ [voiceTraceContext]: capturedOptions[voiceTraceContext],
1290
+ })), control));
1291
+ };
1122
1292
  const ledger = createHarnessRunExecutionLedger(spec.ports, async (input, signal, steps) => {
1123
1293
  // Journal correlation enriches scheduling recovery, but it must not turn
1124
1294
  // an otherwise executable durable run into a failed one when the journal
@@ -1168,6 +1338,7 @@ export function createHarness(spec) {
1168
1338
  }
1169
1339
  }, { checkpoints: spec.durability?.checkpoints, requireApprovalIdentity: requiresApprovalIdentity });
1170
1340
  const approvalService = Object.freeze({
1341
+ listPending: approvals.listPending,
1171
1342
  inspect: approvals.inspect,
1172
1343
  async grant(id) {
1173
1344
  let approval;
@@ -1209,8 +1380,10 @@ export function createHarness(spec) {
1209
1380
  });
1210
1381
  return Object.freeze({
1211
1382
  turn,
1383
+ begin,
1212
1384
  accept: ledger.accept,
1213
1385
  get: ledger.get,
1386
+ inspectRun: (runId, options) => inspectHarnessRun(spec.ports, runId, options),
1214
1387
  status: ledger.status,
1215
1388
  list: ledger.list,
1216
1389
  cancel: ledger.cancel,
package/dist/index.d.ts CHANGED
@@ -2,9 +2,11 @@
2
2
  export * from "./ports.js";
3
3
  export * from "./runtime-adapter.js";
4
4
  export * from "./harness.js";
5
+ export { HarnessSpeculationConflictError, HarnessSpeculationUnsupportedError, type HarnessSpeculativeTurn, } from "./speculative.js";
5
6
  export { HARNESS_RUN_STATUSES, HarnessApprovalPause, HarnessInjectedCrash, createHarnessRunLedger, ensureHarnessDurabilitySchema, type HarnessCheckpoint, type HarnessRun, type HarnessRunAcceptance, type HarnessRunLedger, type HarnessRunLedgerOptions, type HarnessRunListOptions, type HarnessRunStatus, } from "./durable.js";
6
- export { HARNESS_APPROVAL_STATUSES, HarnessApprovalError, HarnessAuthorizationError, type HarnessApproval, type HarnessApprovalLedger, type HarnessApprovalStatus, } from "./approvals.js";
7
+ export { HARNESS_APPROVAL_STATUSES, HarnessApprovalError, HarnessAuthorizationError, type HarnessApproval, type HarnessApprovalLedger, type HarnessApprovalStatus, type HarnessPendingApprovalSummary, type HarnessPendingApprovalPage, type HarnessPendingApprovalListOptions, } from "./approvals.js";
7
8
  export * from "./steps.js";
9
+ export type { HarnessRunInspection, HarnessRunInspectionOptions } from "./diagnostics.js";
8
10
  export * from "./lifecycle.js";
9
11
  export * from "./channel.js";
10
12
  export * from "./scheduling.js";
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export * from "./runtime-adapter.js";
9
9
  // "@telnyx/agent-harness/node" subpath (internal code from
10
10
  // "./node-adapter.js"). The clean-consumer CI check enforces both directions.
11
11
  export * from "./harness.js";
12
+ export { HarnessSpeculationConflictError, HarnessSpeculationUnsupportedError, } from "./speculative.js";
12
13
  export { HARNESS_RUN_STATUSES, HarnessApprovalPause, HarnessInjectedCrash, createHarnessRunLedger, ensureHarnessDurabilitySchema, } from "./durable.js";
13
14
  export { HARNESS_APPROVAL_STATUSES, HarnessApprovalError, HarnessAuthorizationError, } from "./approvals.js";
14
15
  export * from "./steps.js";
package/dist/ports.d.ts CHANGED
@@ -17,6 +17,20 @@ export interface AgentHarnessAuthorizationIdentity {
17
17
  /** Versioned private authorization capability required for approval decisions. */
18
18
  export interface HarnessAuthorizationPort {
19
19
  identity(): AgentHarnessAuthorizationIdentity | undefined;
20
+ /**
21
+ * Optional explicit authority to enumerate minimal pending summaries, including
22
+ * pagination lookahead, for this actor/organization/run. An individual read
23
+ * allow is not collection authority. Hosts must opt in and enforce run access.
24
+ */
25
+ authorizePendingApprovalSummaries?(request: Readonly<{
26
+ identity: AgentHarnessAuthorizationIdentity;
27
+ actorId: string;
28
+ action: "agent_harness.approvals.list_pending";
29
+ resource: string;
30
+ runId: string;
31
+ }>): Promise<Readonly<{
32
+ kind: "allow" | "deny" | "unavailable";
33
+ }>>;
20
34
  authorize(request: Readonly<{
21
35
  identity: AgentHarnessAuthorizationIdentity;
22
36
  action: string;
@@ -0,0 +1,36 @@
1
+ /** In-memory ownership and commitment; never opens a storage transaction. */
2
+ export declare class HarnessSpeculationConflictError extends Error {
3
+ readonly code: "HARNESS_SPECULATION_CONFLICT";
4
+ constructor();
5
+ }
6
+ export declare class HarnessSpeculationUnsupportedError extends Error {
7
+ readonly code: "HARNESS_SPECULATION_UNSUPPORTED";
8
+ constructor(detail: string);
9
+ }
10
+ /** One speculative direct turn. Text is provisional until commit succeeds. */
11
+ export interface HarnessSpeculativeTurn<Result = {
12
+ text: string;
13
+ }> {
14
+ /** Single-consumer, provisional text. Consuming it must not cause durable effects. */
15
+ readonly textStream: AsyncIterable<string>;
16
+ /** Observed internally even when a caller discards without awaiting it. */
17
+ readonly result: Promise<Result>;
18
+ /** First decision wins. Same text returns the same promise; different text rejects. */
19
+ commit(confirmed: string): Promise<Result>;
20
+ /** True for a discarded turn (including repeats), false after commit won. */
21
+ discard(): boolean;
22
+ }
23
+ /** FIFO exclusive reservations; ordinary callers still run concurrently. */
24
+ export declare function serializeTurn<T>(activation: object, work: () => Promise<T>, exclusive?: boolean, signal?: AbortSignal, onQueuedCancel?: () => Promise<never>): Promise<T>;
25
+ export interface SpeculativeControl {
26
+ readonly signal: AbortSignal;
27
+ assertLive(): void;
28
+ fail(error: unknown): void;
29
+ /** Retain ownership until an issued post-commit host effect settles. */
30
+ write<T>(work: () => Promise<T>): Promise<T>;
31
+ /** Resolves to the confirmed input; rejects on abandonment. */
32
+ confirmed(): Promise<string>;
33
+ text(delta: string): void;
34
+ }
35
+ export declare function speculativeTurn<Result>(activation: object, signal: AbortSignal | undefined, cancellation: () => Error, execute: (control: SpeculativeControl) => Promise<Result>): HarnessSpeculativeTurn<Result>;
36
+ //# sourceMappingURL=speculative.d.ts.map