@modelprofile.com/flexharness 15.0.0 → 16.0.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.
package/ts/interfaces.ts CHANGED
@@ -547,6 +547,7 @@ export type TFlexSlashCommandRegistration<TScope> =
547
547
  | IFlexSlashCommandTemplateRegistration
548
548
  | IFlexSlashCommandHandlerRegistration<TScope>;
549
549
 
550
+ /** `promptId` names template and `/init` admissions; results that admit no prompt ignore it. */
550
551
  export interface IFlexSlashCommandExecutionOptions extends IFlexPromptOptions {
551
552
  signal?: AbortSignal;
552
553
  }
@@ -600,6 +601,8 @@ export interface IFlexPromptQueueEntry {
600
601
  /** The prompt's `promptId`, when it named one. */
601
602
  promptId?: string;
602
603
  scheduleKey?: string;
604
+ /** This entry answers existing context without adding a prompt message. */
605
+ continuation?: true;
603
606
  /** The entry is a cache-warm prompt; see {@link IFlexMessage.cacheWarm}. */
604
607
  cacheWarm?: true;
605
608
  startedAt?: string;
@@ -608,8 +611,9 @@ export interface IFlexPromptQueueEntry {
608
611
  /**
609
612
  * On a finished entry: whether its prompt, the steers its run took in and what its model
610
613
  * produced stay in the model context of later turns. True for a completed run and a run
611
- * stopped after its model produced output; false for a failed run, a run stopped before any
612
- * model output and a prompt that never ran. Send the prompt again to have it answered.
614
+ * stopped or failed after its model produced output; false for an unsuccessful run without
615
+ * model output and a prompt that never ran. For a continuation, this describes only the new
616
+ * run's history; it does not change the retention of the existing input.
613
617
  */
614
618
  keptInContext?: boolean;
615
619
  /**
@@ -620,10 +624,10 @@ export interface IFlexPromptQueueEntry {
620
624
  unappliedSteerIds?: string[];
621
625
  }
622
626
 
623
- export interface IFlexPromptResult {
627
+ /** The result of answering existing context, without adding a prompt message. */
628
+ export interface IFlexContinuePromptResult {
624
629
  runId: string;
625
630
  sessionId: string;
626
- userMessage: IFlexMessage;
627
631
  /** The run's last assistant message; a steered run completed earlier ones as it took steers in. */
628
632
  assistantMessage: IFlexMessage;
629
633
  model: IFlexModelIdentity;
@@ -634,6 +638,10 @@ export interface IFlexPromptResult {
634
638
  unappliedSteerIds: string[];
635
639
  }
636
640
 
641
+ export interface IFlexPromptResult extends IFlexContinuePromptResult {
642
+ userMessage: IFlexMessage;
643
+ }
644
+
637
645
  /** A message for the running run of a session, taken in at the run's next step boundary. */
638
646
  export interface IFlexSteerInput {
639
647
  /** Caller-chosen identity, unique among the steers of the run. */
@@ -647,12 +655,13 @@ export interface IFlexSteerAdmission {
647
655
  runId: string;
648
656
  }
649
657
 
650
- export interface IFlexPromptQueueAdmission {
658
+ export interface IFlexPromptQueueAdmission<TResult extends IFlexContinuePromptResult = IFlexPromptResult> {
651
659
  queueId: string;
652
- completion: Promise<IFlexPromptResult>;
660
+ completion: Promise<TResult>;
653
661
  }
654
662
 
655
- export interface IFlexPromptAdmission extends IFlexPromptQueueAdmission {
663
+ export interface IFlexPromptAdmission<TResult extends IFlexContinuePromptResult = IFlexPromptResult>
664
+ extends IFlexPromptQueueAdmission<TResult> {
656
665
  runId: string;
657
666
  }
658
667
 
@@ -673,6 +682,13 @@ export interface IFlexEnqueuePromptOptions extends IFlexPromptOptions {
673
682
  steers?: readonly IFlexSteerInput[];
674
683
  }
675
684
 
685
+ /** Options for continuation through the same FIFO, optionally with scheduled execution. */
686
+ export interface IFlexContinuePromptOptions extends Omit<IFlexEnqueuePromptOptions, 'promptId'> {
687
+ scheduleKey?: string;
688
+ /** Requires scheduleKey; defaults to 50 ms when scheduled. */
689
+ debounceMs?: number;
690
+ }
691
+
676
692
  export interface IFlexScheduledPromptAdmission extends IFlexPromptAdmission {
677
693
  scheduleKey: string;
678
694
  }
@@ -1260,7 +1276,8 @@ export interface IFlexScopeSnapshot {
1260
1276
  export interface IFlexTerminalProjection {
1261
1277
  runId: string;
1262
1278
  status: 'completed' | 'failed' | 'cancelled';
1263
- userMessage: IFlexMessage;
1279
+ /** Absent for a continuation, which creates no prompt message. */
1280
+ userMessage?: IFlexMessage;
1264
1281
  assistantMessage: IFlexMessage;
1265
1282
  model?: IFlexModelIdentity;
1266
1283
  /** The usage the provider reported for the run's model calls, whatever the status. */
@@ -1288,7 +1305,8 @@ export interface IFlexAffectedWorkspace {
1288
1305
 
1289
1306
  interface IFlexReversionSegmentV2 {
1290
1307
  runId: string;
1291
- userMessageId: string;
1308
+ /** Absent for an assistant-only continuation segment. */
1309
+ userMessageId?: string;
1292
1310
  status: TFlexReversionSegmentStatus;
1293
1311
  contextAvailable: boolean;
1294
1312
  eventIds: string[];
package/ts/plugins.ts CHANGED
@@ -159,6 +159,7 @@ export type {
159
159
  IAgentRetryEvent,
160
160
  IAgentRunResult,
161
161
  IAgentSession,
162
+ IAgentContinuationSession,
162
163
  IAgentSessionOptions,
163
164
  IAgentToolCallUpdateEvent,
164
165
  IAgentToolExecutionOptions,
package/ts/readme.md CHANGED
@@ -498,7 +498,7 @@ Sending requires the exact active parent run, its currently delegated direct chi
498
498
 
499
499
  Before creating or resuming a child, FlexHarness requests permission on the parent run with `kind: 'subagent.start'`, the parent `toolCallId`, and separately bounded harness-owned agent/task metadata. Its metadata includes `childSessionId`, the exact ID reserved for this call: FlexHarness derives it deterministically when `taskId` is omitted and copies the supplied candidate when `taskId` is present. A resume also retains that candidate as `taskId`. This block is not truncated by `toolOutputLimits`. The reserved ID binds permission handling before child creation but does not prove that the child exists or is owned: applications must treat the later delegated admission context as authoritative. The controller answers through the normal permission APIs. This request has no `rememberKey`, so `always` is invalid; controllers use `once` or `reject`.
500
500
 
501
- Each new invocation creates a durable child `IFlexSession` with immutable `parentSessionId`, origin `parentRunId`, origin `parentToolCallId`, `agent`, and `depth`. New public roots persist `depth: 0`; legacy schema-1 roots may omit it. These fields are harness-owned; public `createSession()` accepts `sessionId`, `sessionGenerationId`, `configurationRef`, and `title`. A child inherits its parent's `configurationRef` and cannot resume under a different reference. Child sessions reject direct `prompt()`, `startPrompt()`, `enqueuePrompt()`, and `schedulePrompt()` calls and run only through the `delegate` tool. The model and tool resolver contexts receive optional immutable `parentSessionId` and `agent` values so integrations can apply agent-specific model and tool policy. Child prompts use the definition's `modelHint`, `system`, and `maxSteps`.
501
+ Each new invocation creates a durable child `IFlexSession` with immutable `parentSessionId`, origin `parentRunId`, origin `parentToolCallId`, `agent`, and `depth`. New public roots persist `depth: 0`; legacy schema-1 roots may omit it. These fields are harness-owned; public `createSession()` accepts `sessionId`, `sessionGenerationId`, `configurationRef`, and `title`. A child inherits its parent's `configurationRef` and cannot resume under a different reference. Child sessions reject direct `continuePrompt()`, `prompt()`, `startPrompt()`, `enqueuePrompt()`, and `schedulePrompt()` calls and run only through the `delegate` tool. The model and tool resolver contexts receive optional immutable `parentSessionId` and `agent` values so integrations can apply agent-specific model and tool policy. Child prompts use the definition's `modelHint`, `system`, and `maxSteps`.
502
502
 
503
503
  The parent tool part receives `childSessionId` as soon as the child is acquired, so a host can offer an authorized live transcript. A foreground call also adds the resolved child model to its part and returns bounded JSON:
504
504
 
@@ -717,6 +717,55 @@ The reservation save is the admission point. A save failure produces no start ev
717
717
 
718
718
  `abort()` returns `true` only while cancellation is still accepted. Terminal persistence is the run's commit point; once it starts, `abort()` returns `false` and the already-fixed terminal outcome completes while the session remains busy.
719
719
 
720
+ ### Continuing Without New Input
721
+
722
+ Use `continuePrompt()` after a failed or stopped run whose input remains in context
723
+ (`keptInContext: true`), when the conversation still needs an answer:
724
+
725
+ ```typescript
726
+ const continuation = await harness.continuePrompt(scopeId, sessionId, {
727
+ modelHint: 'available-model',
728
+ });
729
+ console.log(continuation.queueId);
730
+ const result = await continuation.completion;
731
+ console.log(result.assistantMessage.parts);
732
+ ```
733
+
734
+ The signature is `continuePrompt(scopeId: string, sessionId: string,
735
+ options?: IFlexContinuePromptOptions): Promise<IFlexPromptQueueAdmission<IFlexContinuePromptResult>>`.
736
+ It accepts `modelHint`, `system`, `maxSteps` and initial `steers`, plus optional
737
+ `scheduleKey` and `debounceMs` for the same scheduling behavior as `schedulePrompt()`.
738
+ `debounceMs` requires a schedule key and defaults to 50 ms when scheduled. There is
739
+ no `prompt` or `promptId`: continuation creates a new run and an assistant message
740
+ without appending or modifying a user message. Its result has the prompt result's
741
+ fields except `userMessage`. Explicit steers still become user messages normally.
742
+
743
+ Admission returns `{ queueId, completion }` once queued, like `enqueuePrompt()`.
744
+ The entry and its `prompt.*` events carry `continuation: true`. Inspect its `runId`
745
+ after `prompt.started`, or read it through `getPromptQueueEntry()`. Use the same
746
+ `cancelPrompt()`, `cancelScheduledPrompt()`, `abort()` and `steerPrompt()` methods.
747
+ Queue bounds and FIFO order, permissions, resource cleanup, compaction, usage
748
+ accounting and provider limits follow the ordinary run path.
749
+
750
+ At the front of the FIFO, the harness validates the visible transcript and model
751
+ context with undone and excluded runs filtered out, before committing an undone
752
+ branch or reserving a generation or audit message. A refusal preserves redo history.
753
+ Empty context, an already answered turn, and an unsuccessful input that did not stay
754
+ in context refuse with `FlexHarnessValidationError` through `completion`. A retained
755
+ failure whose model context ends in partial assistant text with no tool call is also
756
+ refused; `keptInContext: true` alone does not guarantee it can be continued. An earlier
757
+ queued answer can therefore invalidate a waiting continuation. Cancellation before
758
+ execution leaves the retained input available for another continuation. Continuing never replays
759
+ previous tool calls; unknown external effects still require reconciliation.
760
+
761
+ Existing projections and Agent event stores remain readable without migration.
762
+ Assistant-only runs require two optional fields in the existing schema-3 projection:
763
+ a staged terminal omits `userMessage`, and its reversion segment omits
764
+ `userMessageId`. Requiring either would invent a user message. Generation claims
765
+ use existing events without a new `user-message` event. Older strict projection
766
+ readers cannot open a session containing these continuation records; do not mix
767
+ versions or downgrade those sessions.
768
+
720
769
  ### Steering a Running Turn
721
770
 
722
771
  `steerPrompt()` hands a message to the running run of a prompt instead of queueing it behind that run:
@@ -823,7 +872,7 @@ Two horizons bound undo. Retention pruning removes the oldest complete visible u
823
872
 
824
873
  Session metadata archival through `updateSession(..., { archived: true })` only sets `archivedAt`. It does not archive Agent events, retire captures, or remove undo history.
825
874
 
826
- `executeSlashCommand()` is the authoritative parser and lookup boundary. Its result distinguishes `not-command`, `malformed`, `unknown`, completed `operation`, bounded `handler-result`, and `prompt-admission`. A prompt admission contains the normal `{ queueId, runId, completion }`; await `admission.completion` for the model result. Unknown commands are never admitted as literal prompts. Known unavailable commands and invalid arguments throw typed FlexHarness errors. Options accept `modelHint`, `system`, `maxSteps`, and `signal`; commands do not accept attachments. `/compact` takes `modelHint` and `signal` only and refuses `system` and `maxSteps` with `FlexHarnessValidationError`. Aborting a template or `init` execution cancels its exact queued or started prompt without affecting another queue entry.
875
+ `executeSlashCommand()` is the authoritative parser and lookup boundary. Its result distinguishes `not-command`, `malformed`, `unknown`, completed `operation`, bounded `handler-result`, and `prompt-admission`. A prompt admission contains the normal `{ queueId, runId, completion }`; await `admission.completion` for the model result. Unknown commands are never admitted as literal prompts. Known unavailable commands and invalid arguments throw typed FlexHarness errors. Options accept `promptId`, `modelHint`, `system`, `maxSteps`, and `signal`; commands do not accept attachments. For templates and `/init`, `promptId` follows the normal prompt contract: a non-empty string of at most 512 UTF-8 bytes, recorded on the queue entry and native user message (including exact transcript reads after restart or with `includeUndone`), with duplicate outstanding ids refused by `FlexHarnessDuplicatePromptError`. Results that admit no prompt (`operation`, `handler-result`, `unknown`, `not-command`, `malformed`) ignore `promptId` without recording or reserving it. `/compact` uses `modelHint` and `signal` and refuses `system` and `maxSteps` with `FlexHarnessValidationError`. Aborting a template or `init` execution cancels its exact queued or started prompt without affecting another queue entry.
827
876
 
828
877
  Templates replace every `$ARGUMENTS` with untouched raw argument text. `$1` through the highest referenced positional placeholder use tokenized arguments, with the highest position receiving all remaining tokens joined by spaces. Missing positions become empty. A template with no placeholders appends non-empty raw arguments after a blank line. `/init` uses the OpenCode 1.18.15 `AGENTS.md` initialization prompt with provider-neutral active-workspace wording.
829
878
 
@@ -1010,7 +1059,7 @@ if (started.status === 'started') {
1010
1059
 
1011
1060
  **Read-only.** An imported session never runs, and nothing writes model context for it: it has no Agent events and no tool jobs. Every operation that would change its history or model context throws `FlexHarnessImportedSessionError` (code `FLEX_SESSION_READ_ONLY`):
1012
1061
 
1013
- - `prompt`, `startPrompt`, `enqueuePrompt` and `schedulePrompt`;
1062
+ - `prompt`, `startPrompt`, `enqueuePrompt`, `schedulePrompt` and `continuePrompt`;
1014
1063
  - `executeSlashCommand` and `compactSession`;
1015
1064
  - `undoSession` and `redoSession`;
1016
1065
  - `pushRuntimeEvent`, `reconcileToolExecution` and `archiveSessionEvents`;
package/ts/utils.json.ts CHANGED
@@ -762,6 +762,7 @@ interface IValidatedMessageIdentity {
762
762
  runId: string;
763
763
  role: 'user' | 'assistant';
764
764
  status: 'streaming' | 'completed' | 'failed' | 'cancelled';
765
+ steerId?: string;
765
766
  }
766
767
 
767
768
  interface IValidatedSessionIdentity {
@@ -932,6 +933,7 @@ function validateMessage(value: unknown, path: string): IValidatedMessageIdentit
932
933
  runId,
933
934
  role: message.role,
934
935
  status: message.status as IValidatedMessageIdentity['status'],
936
+ ...(message.steerId === undefined ? {} : { steerId: message.steerId as string }),
935
937
  };
936
938
  }
937
939
 
@@ -1226,7 +1228,7 @@ export function assertFlexScopeSnapshot(value: unknown): asserts value is IFlexS
1226
1228
 
1227
1229
  interface IValidatedProjectionBody {
1228
1230
  messages: IValidatedMessageIdentity[];
1229
- stagedUsers: IValidatedMessageIdentity[];
1231
+ stagedMessages: IValidatedMessageIdentity[];
1230
1232
  }
1231
1233
 
1232
1234
  function validateProjectionBody(snapshot: Record<string, unknown>): IValidatedProjectionBody {
@@ -1240,7 +1242,7 @@ function validateProjectionBody(snapshot: Record<string, unknown>): IValidatedPr
1240
1242
  }
1241
1243
  const runIds = new Set<string>();
1242
1244
  const stagedMessageIds = new Set<string>();
1243
- const stagedUsers: IValidatedMessageIdentity[] = [];
1245
+ const stagedMessages: IValidatedMessageIdentity[] = [];
1244
1246
  for (let index = 0; index < snapshot.stagedTerminals.length; index++) {
1245
1247
  const path = `$snapshot.stagedTerminals[${index}]`;
1246
1248
  const terminal = requireRecord(snapshot.stagedTerminals[index], path);
@@ -1266,26 +1268,33 @@ function validateProjectionBody(snapshot: Record<string, unknown>): IValidatedPr
1266
1268
  if (!['completed', 'failed', 'cancelled'].includes(String(terminal.status))) {
1267
1269
  throw new FlexHarnessStoreFormatError(`${path}.status is invalid.`);
1268
1270
  }
1269
- const user = validateMessage(terminal.userMessage, `${path}.userMessage`);
1271
+ const user = terminal.userMessage === undefined
1272
+ ? undefined
1273
+ : validateMessage(terminal.userMessage, `${path}.userMessage`);
1270
1274
  const assistant = validateMessage(terminal.assistantMessage, `${path}.assistantMessage`);
1271
- if (user.role !== 'user' || assistant.role !== 'assistant') {
1275
+ if ((user && user.role !== 'user') || assistant.role !== 'assistant') {
1272
1276
  throw new FlexHarnessStoreFormatError(`${path} has invalid terminal message roles.`);
1273
1277
  }
1274
- if (user.runId !== runId || assistant.runId !== runId || user.sessionId !== assistant.sessionId) {
1278
+ if ((user && (user.runId !== runId || user.sessionId !== assistant.sessionId)) || assistant.runId !== runId) {
1275
1279
  throw new FlexHarnessStoreFormatError(`${path} has uncorrelated terminal messages.`);
1276
1280
  }
1277
1281
  if (assistant.status !== terminal.status) {
1278
1282
  throw new FlexHarnessStoreFormatError(`${path}.status does not match its assistant message.`);
1279
1283
  }
1280
- if (user.status !== terminal.status) {
1284
+ if (!user && visibleMessages.some((message) =>
1285
+ message.runId === runId && message.role === 'user' && message.steerId === undefined)) {
1286
+ throw new FlexHarnessStoreFormatError(`${path} omits its projected prompt message.`);
1287
+ }
1288
+ if (user && user.status !== terminal.status) {
1281
1289
  throw new FlexHarnessStoreFormatError(`${path}.status does not match its user message.`);
1282
1290
  }
1283
- stagedUsers.push(user);
1284
- projectionSessionId ??= user.sessionId;
1285
- if (user.sessionId !== projectionSessionId) {
1291
+ if (user) stagedMessages.push(user);
1292
+ stagedMessages.push(assistant);
1293
+ projectionSessionId ??= assistant.sessionId;
1294
+ if (assistant.sessionId !== projectionSessionId) {
1286
1295
  throw new FlexHarnessStoreFormatError(`${path} belongs to another session.`);
1287
1296
  }
1288
- for (const messageId of [user.messageId, assistant.messageId]) {
1297
+ for (const messageId of [...(user ? [user.messageId] : []), assistant.messageId]) {
1289
1298
  if (stagedMessageIds.has(messageId)) {
1290
1299
  throw new FlexHarnessStoreFormatError(
1291
1300
  `Snapshot staged terminals contain duplicate message "${messageId}".`,
@@ -1298,7 +1307,7 @@ function validateProjectionBody(snapshot: Record<string, unknown>): IValidatedPr
1298
1307
  requireOptionalString(terminal.finishReason, `${path}.finishReason`);
1299
1308
  if (terminal.steps !== undefined) requireNonNegativeInteger(terminal.steps, `${path}.steps`);
1300
1309
  }
1301
- return { messages: visibleMessages, stagedUsers };
1310
+ return { messages: visibleMessages, stagedMessages };
1302
1311
  }
1303
1312
 
1304
1313
  export function assertFlexProjectionSnapshotV1(
@@ -1358,7 +1367,7 @@ export function assertFlexProjectionSnapshotV2(
1358
1367
  'workspaceReference',
1359
1368
  ], path);
1360
1369
  const runId = requireString(segment.runId, `${path}.runId`);
1361
- const userMessageId = requireString(segment.userMessageId, `${path}.userMessageId`);
1370
+ const userMessageId = requireOptionalString(segment.userMessageId, `${path}.userMessageId`);
1362
1371
  if (segmentRunIds.has(runId)) {
1363
1372
  throw new FlexHarnessStoreFormatError(`Snapshot contains duplicate reversion run "${runId}".`);
1364
1373
  }
@@ -1414,12 +1423,19 @@ export function assertFlexProjectionSnapshotV2(
1414
1423
  throw new FlexHarnessStoreFormatError(`${path}.workspaceReference exceeds its byte limit.`);
1415
1424
  }
1416
1425
  }
1417
- const correlatedUsers = new Set([...projectionBody.messages, ...projectionBody.stagedUsers]
1426
+ const projectedMessages = [...projectionBody.messages, ...projectionBody.stagedMessages];
1427
+ if (userMessageId === undefined && projectedMessages.some((message) =>
1428
+ message.runId === runId && message.role === 'user' && message.steerId === undefined)) {
1429
+ throw new FlexHarnessStoreFormatError(`${path} omits its projected prompt identity.`);
1430
+ }
1431
+ const correlatedMessages = new Set(projectedMessages
1418
1432
  .filter((message) =>
1419
- message.role === 'user' && message.runId === runId && message.messageId === userMessageId)
1433
+ message.runId === runId && (userMessageId === undefined
1434
+ ? message.role === 'assistant'
1435
+ : message.role === 'user' && message.messageId === userMessageId))
1420
1436
  .map((message) => `${message.runId}:${message.messageId}`));
1421
- if (correlatedUsers.size !== 1) {
1422
- throw new FlexHarnessStoreFormatError(`${path} does not match exactly one projected user message.`);
1437
+ if (userMessageId === undefined ? correlatedMessages.size === 0 : correlatedMessages.size !== 1) {
1438
+ throw new FlexHarnessStoreFormatError(`${path} does not match its projected turn messages.`);
1423
1439
  }
1424
1440
  }
1425
1441
  requireNonNegativeInteger(snapshot.revertCursor, '$snapshot.revertCursor');
@@ -1843,7 +1859,7 @@ export function assertFlexProjectionSnapshotV3(
1843
1859
  const segment = entry as Record<string, unknown>;
1844
1860
  return {
1845
1861
  runId: segment.runId,
1846
- userMessageId: segment.userMessageId,
1862
+ ...(segment.userMessageId === undefined ? {} : { userMessageId: segment.userMessageId }),
1847
1863
  status: segment.status,
1848
1864
  contextAvailable: segment.contextAvailable,
1849
1865
  eventIds: segment.eventIds,
@@ -1901,7 +1917,7 @@ function assertProjectionSession(
1901
1917
  }
1902
1918
  for (const terminal of snapshot.stagedTerminals) {
1903
1919
  if (
1904
- terminal.userMessage.sessionId !== sessionId
1920
+ (terminal.userMessage !== undefined && terminal.userMessage.sessionId !== sessionId)
1905
1921
  || terminal.assistantMessage.sessionId !== sessionId
1906
1922
  ) {
1907
1923
  throw new FlexHarnessStoreFormatError(
@@ -376,7 +376,7 @@ export function validateSlashCommandExecutionOptions(
376
376
  throw new FlexHarnessValidationError('Slash command execution options must be a plain object.');
377
377
  }
378
378
  const unsupported = Object.keys(options)
379
- .find((key) => !['modelHint', 'system', 'maxSteps', 'signal'].includes(key));
379
+ .find((key) => !['promptId', 'modelHint', 'system', 'maxSteps', 'signal'].includes(key));
380
380
  if (unsupported) {
381
381
  throw new FlexHarnessValidationError(
382
382
  `Slash command execution options do not support "${unsupported}".`,
@@ -386,6 +386,7 @@ export function validateSlashCommandExecutionOptions(
386
386
  throw new FlexHarnessValidationError('Slash command signal must be an AbortSignal.');
387
387
  }
388
388
  const promptOptions: IFlexPromptOptions = {
389
+ ...(options.promptId === undefined ? {} : { promptId: options.promptId }),
389
390
  ...(options.modelHint === undefined ? {} : { modelHint: options.modelHint }),
390
391
  ...(options.system === undefined ? {} : { system: options.system }),
391
392
  ...(options.maxSteps === undefined ? {} : { maxSteps: options.maxSteps }),