You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/08/31 19:00:54 UTC

(maka) 02/02: fix(runtime): complete form interaction adapter seam

This is an automated email from the ASF dual-hosted git repository.

me2seeks pushed a commit to branch feat/4364-runtime-form-interactions
in repository https://gitbox.apache.org/repos/asf/maka.git

commit d8bae9fffaa501b5fa0de383371ece11421bc3a2
Author: me2seeks <me...@gmail.com>
AuthorDate: Mon Aug 31 23:17:53 2026 +0800

    fix(runtime): complete form interaction adapter seam
    
    Expose one closed decoder for renderer-to-runtime form responses so surface adapters do not copy protocol validation. Queue the same canonical continuity refresh for form requests that user questions already receive.
    
    Refs #4364.
    
    Generated-by: OpenAI Codex
---
 packages/core/src/__tests__/interaction.test.ts    | 28 ++++++++++++++++++++++
 packages/core/src/interaction.ts                   | 26 ++++++++++++++++++++
 .../src/server/root-turn-coordinator.ts            |  2 +-
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/__tests__/interaction.test.ts b/packages/core/src/__tests__/interaction.test.ts
index 868f6e5e01..ee012a4571 100644
--- a/packages/core/src/__tests__/interaction.test.ts
+++ b/packages/core/src/__tests__/interaction.test.ts
@@ -29,6 +29,7 @@ import {
   InteractionPermissionProjectionError,
   decodeInteractionAnswer,
   decodeInteractionCanonicalOutcome,
+  decodeInteractionFormResponse,
   decodeInteractionRequest,
   interactionCanonicalOutcomesEquivalent,
   isInteractionAnswerValidForRequest,
@@ -1256,4 +1257,31 @@ describe('Interaction decoding and validity', () => {
       false,
     );
   });
+
+  test('decodes form responses as a closed renderer-to-runtime shape', () => {
+    assert.deepEqual(
+      decodeInteractionFormResponse({
+        requestId: 'form-1',
+        action: 'accept',
+        values: { replicas: 3, regions: ['us', 'eu'] },
+      }),
+      {
+        requestId: 'form-1',
+        action: 'accept',
+        values: { replicas: 3, regions: ['us', 'eu'] },
+      },
+    );
+    assert.deepEqual(decodeInteractionFormResponse({ requestId: 'form-1', action: 'decline' }), {
+      requestId: 'form-1',
+      action: 'decline',
+    });
+    for (const invalid of [
+      { requestId: 'form-1', action: 'accept' },
+      { requestId: 'form-1', action: 'cancel', values: {} },
+      { requestId: 'form-1', action: 'accept', values: {}, extra: true },
+      { requestId: '', action: 'decline' },
+    ]) {
+      assert.throws(() => decodeInteractionFormResponse(invalid));
+    }
+  });
 });
diff --git a/packages/core/src/interaction.ts b/packages/core/src/interaction.ts
index 09dad93548..cbf2ce0af7 100644
--- a/packages/core/src/interaction.ts
+++ b/packages/core/src/interaction.ts
@@ -301,6 +301,12 @@ const FORM_ACCEPT_ANSWER_SHAPE = defineObjectShape<
 const FORM_EMPTY_ANSWER_SHAPE = defineObjectShape<
   Extract<InteractionFormAnswer, { action: 'decline' | 'cancel' }>
 >()(['kind', 'action'], []);
+const FORM_ACCEPT_RESPONSE_SHAPE = defineObjectShape<
+  Extract<InteractionFormResponse, { action: 'accept' }>
+>()(['requestId', 'action', 'values'], []);
+const FORM_EMPTY_RESPONSE_SHAPE = defineObjectShape<
+  Extract<InteractionFormResponse, { action: 'decline' | 'cancel' }>
+>()(['requestId', 'action'], []);
 const SANDBOX_BOUNDARY_ANSWER_SHAPE = defineObjectShape<InteractionSandboxBoundaryAnswer>()(
   ['kind', 'decision'],
   [],
@@ -455,6 +461,26 @@ export function decodeInteractionAnswer(value: unknown): InteractionAnswer {
   return deepFreeze(answer);
 }
 
+export function decodeInteractionFormResponse(value: unknown): InteractionFormResponse {
+  const record = plainRecord(value, 'Interaction form response');
+  const requestId = boundedString(record.requestId, 'requestId', INTERACTION_ID_MAX_BYTES);
+  const action = oneOf(record.action, ['accept', 'decline', 'cancel'] as const, 'form action');
+  if (action === 'accept') {
+    exact(record, FORM_ACCEPT_RESPONSE_SHAPE, 'accepted form response');
+    const answer = decodeInteractionAnswer({
+      kind: 'form',
+      action,
+      values: record.values,
+    });
+    if (answer.kind !== 'form' || answer.action !== 'accept') {
+      throw new Error('Invalid accepted form response');
+    }
+    return deepFreeze({ requestId, action, values: answer.values });
+  }
+  exact(record, FORM_EMPTY_RESPONSE_SHAPE, 'empty form response');
+  return deepFreeze({ requestId, action });
+}
+
 export function decodeInteractionCanonicalOutcome(value: unknown): InteractionCanonicalOutcome {
   const record = plainRecord(value, 'Interaction canonical outcome');
   let outcome: InteractionCanonicalOutcome;
diff --git a/packages/runtime-host/src/server/root-turn-coordinator.ts b/packages/runtime-host/src/server/root-turn-coordinator.ts
index 6de6170635..e6b8e00231 100644
--- a/packages/runtime-host/src/server/root-turn-coordinator.ts
+++ b/packages/runtime-host/src/server/root-turn-coordinator.ts
@@ -2388,7 +2388,7 @@ export class RootTurnCoordinator implements HostedExecutionAuthority {
           await this.continuity.acceptRuntimeEvent(input.sessionId, active.runId, event);
         } else if (isInteractionAnswerAck(event)) {
           await this.continuity.refreshCanonical(input.sessionId);
-        } else if (event.type === 'user_question_request') {
+        } else if (event.type === 'user_question_request' || event.type === 'form_request') {
           this.continuity.enqueueCanonicalRefresh(input.sessionId);
         }
       }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org