maka-agent 0.2.0-dev.14.20260902 → 0.2.0-dev.16.20260903

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 (160) hide show
  1. package/dist/cli-core.js +0 -1
  2. package/dist/pi-transcript.js +48 -0
  3. package/dist/pi-tui-form-interaction.js +497 -0
  4. package/dist/pi-tui-pickers.js +256 -55
  5. package/dist/pi-tui-runner.js +215 -12
  6. package/dist/runtime-host-cli.js +17 -42
  7. package/dist/runtime-host-lifecycle-transaction.js +31 -13
  8. package/dist/runtime-host-managed-lifecycle-manager.js +1 -1
  9. package/dist/runtime-host-onboarding.js +3 -1
  10. package/dist/runtime-host-openrc-service.js +469 -0
  11. package/dist/runtime-host-run-command.js +6 -2
  12. package/dist/runtime-host-service-command.js +3 -3
  13. package/dist/runtime-host-service-management-command.js +43 -15
  14. package/dist/runtime-host-session-channel.js +3 -0
  15. package/dist/runtime-host-session-driver.js +21 -6
  16. package/dist/runtime-host-setup-command.js +30 -31
  17. package/dist/tui-context-refresh.js +103 -0
  18. package/dist/tui-copy-catalog.js +73 -0
  19. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  20. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  21. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  22. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  23. package/node_modules/@maka/core/dist/chat-model-choice.js +4 -0
  24. package/node_modules/@maka/core/dist/computer-use.js +27 -92
  25. package/node_modules/@maka/core/dist/interaction.js +577 -3
  26. package/node_modules/@maka/core/dist/llm-connections.js +4 -0
  27. package/node_modules/@maka/core/dist/local-memory.js +2 -2
  28. package/node_modules/@maka/core/dist/model-call-usage-projection.js +3 -0
  29. package/node_modules/@maka/core/dist/model-metadata.js +26 -7
  30. package/node_modules/@maka/core/dist/model-thinking.js +17 -0
  31. package/node_modules/@maka/core/dist/provider-registry.js +1 -0
  32. package/node_modules/@maka/core/dist/runtime-event.js +23 -1
  33. package/node_modules/@maka/core/dist/scheduled-task.js +4 -0
  34. package/node_modules/@maka/core/dist/session.js +95 -2
  35. package/node_modules/@maka/core/dist/text-sanitize.js +19 -0
  36. package/node_modules/@maka/core/dist/usage-ledger-merge.js +3 -0
  37. package/node_modules/@maka/core/dist/usage-record-schema.js +11 -5
  38. package/node_modules/@maka/core/dist/workhub-creation-intent.js +75 -9
  39. package/node_modules/@maka/core/dist/workhub-session-resolver.js +50 -0
  40. package/node_modules/@maka/core/package.json +2 -0
  41. package/node_modules/@maka/eval/dist/harbor-external-subject.js +2 -1
  42. package/node_modules/@maka/eval/dist/harness-executor.js +3 -2
  43. package/node_modules/@maka/eval/dist/install-preflight.js +39 -10
  44. package/node_modules/@maka/eval/dist/process-termination.js +83 -0
  45. package/node_modules/@maka/mcp/dist/sep-2243.js +3 -3
  46. package/node_modules/@maka/runtime/dist/agent-run.js +6 -3
  47. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +332 -118
  48. package/node_modules/@maka/runtime/dist/ai-sdk-compaction-contract.js +6 -1
  49. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +171 -341
  50. package/node_modules/@maka/runtime/dist/bots/telegram-bridge.js +3 -26
  51. package/node_modules/@maka/runtime/dist/bundled-skill-catalog.generated.js +1 -1
  52. package/node_modules/@maka/runtime/dist/computer-use-codec.js +1 -141
  53. package/node_modules/@maka/runtime/dist/computer-use-tools.js +23 -105
  54. package/node_modules/@maka/runtime/dist/context-budget-policy.js +20 -26
  55. package/node_modules/@maka/runtime/dist/context-budget.js +18 -20
  56. package/node_modules/@maka/runtime/dist/conversation-copy.js +23 -19
  57. package/node_modules/@maka/runtime/dist/cua-frame-state.js +1 -69
  58. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +267 -0
  59. package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +6 -9
  60. package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +67 -23
  61. package/node_modules/@maka/runtime/dist/history-compact-summary-validation.js +11 -18
  62. package/node_modules/@maka/runtime/dist/history-compaction.js +66 -73
  63. package/node_modules/@maka/runtime/dist/interaction-authority.js +69 -0
  64. package/node_modules/@maka/runtime/dist/mcp-tools.js +13 -1
  65. package/node_modules/@maka/runtime/dist/memory-extraction.js +11 -11
  66. package/node_modules/@maka/runtime/dist/model-adapter.js +24 -6
  67. package/node_modules/@maka/runtime/dist/model-factory.js +14 -5
  68. package/node_modules/@maka/runtime/dist/oauth-provider-contracts.js +28 -1
  69. package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +7 -6
  70. package/node_modules/@maka/runtime/dist/opencode-session-header.js +29 -0
  71. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +11 -1
  72. package/node_modules/@maka/runtime/dist/runtime-kernel.js +12 -11
  73. package/node_modules/@maka/runtime/dist/runtime-read-model.js +51 -13
  74. package/node_modules/@maka/runtime/dist/scheduled-task-tools.js +3 -0
  75. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +26 -0
  76. package/node_modules/@maka/runtime/dist/session-manager.js +1 -0
  77. package/node_modules/@maka/runtime/dist/session-projection-helpers.js +16 -1
  78. package/node_modules/@maka/runtime/dist/stream-graph-projection.js +6 -0
  79. package/node_modules/@maka/runtime/dist/stream-graph-read-model.js +5 -0
  80. package/node_modules/@maka/runtime/dist/stream-usage-fallback-fetch.js +74 -0
  81. package/node_modules/@maka/runtime/dist/subscription-credentials.js +51 -4
  82. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +47 -3
  83. package/node_modules/@maka/runtime/dist/system-prompt/main-session-prompt.js +20 -1
  84. package/node_modules/@maka/runtime/dist/test-connection.js +18 -11
  85. package/node_modules/@maka/runtime/dist/tool-runtime.js +183 -0
  86. package/node_modules/@maka/runtime/package.json +1 -0
  87. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +14 -0
  88. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +84 -1
  89. package/node_modules/@maka/runtime-host/dist/client/host-profile.js +68 -51
  90. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  91. package/node_modules/@maka/runtime-host/dist/client/owner-connection-code.js +8 -5
  92. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +342 -36
  93. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +60 -4
  94. package/node_modules/@maka/runtime-host/dist/peer-mesh/index.js +3 -3
  95. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +1 -1
  96. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +48 -60
  97. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +847 -431
  98. package/node_modules/@maka/runtime-host/dist/peer-mesh/owner.js +33 -74
  99. package/node_modules/@maka/runtime-host/dist/peer-mesh/store.js +82 -69
  100. package/node_modules/@maka/runtime-host/dist/peer-reachability/index.js +21 -0
  101. package/node_modules/@maka/runtime-host/dist/peer-reachability/model.js +202 -0
  102. package/node_modules/@maka/runtime-host/dist/peer-reachability/owner.js +119 -0
  103. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +245 -0
  104. package/node_modules/@maka/runtime-host/dist/protocol/agent-graph.js +4 -1
  105. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +68 -0
  106. package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +15 -8
  107. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +7 -23
  108. package/node_modules/@maka/runtime-host/dist/protocol/index.js +47 -1
  109. package/node_modules/@maka/runtime-host/dist/protocol/oauth.js +25 -1
  110. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  111. package/node_modules/@maka/runtime-host/dist/protocol/peer-mesh.js +14 -31
  112. package/node_modules/@maka/runtime-host/dist/protocol/scheduled-task.js +19 -6
  113. package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +36 -1
  114. package/node_modules/@maka/runtime-host/dist/protocol/session-revision.js +11 -2
  115. package/node_modules/@maka/runtime-host/dist/protocol/usage-pricing.js +16 -2
  116. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +51 -2
  117. package/node_modules/@maka/runtime-host/dist/server/access-authority.js +35 -2
  118. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +1 -0
  119. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +12 -10
  120. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +163 -30
  121. package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +38 -14
  122. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +45 -22
  123. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +78 -20
  124. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +4 -2
  125. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +2 -1
  126. package/node_modules/@maka/runtime-host/dist/server/execution-service.js +49 -28
  127. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -6
  128. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +130 -27
  129. package/node_modules/@maka/runtime-host/dist/server/interaction-projection.js +30 -1
  130. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -4
  131. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +16 -3
  132. package/node_modules/@maka/runtime-host/dist/server/oauth-coordinator.js +66 -3
  133. package/node_modules/@maka/runtime-host/dist/server/oauth-execution-authority.js +20 -3
  134. package/node_modules/@maka/runtime-host/dist/server/peer-listener.js +8 -21
  135. package/node_modules/@maka/runtime-host/dist/server/peer-mesh-authority.js +1 -1
  136. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +6 -3
  137. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +72 -12
  138. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +48 -18
  139. package/node_modules/@maka/runtime-host/dist/server/session-turn-access-request-coordinator.js +43 -26
  140. package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +15 -1
  141. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +229 -4
  142. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +146 -8
  143. package/node_modules/@maka/runtime-host/dist/transport/peer-native.js +32 -6
  144. package/node_modules/@maka/runtime-host/package.json +1 -0
  145. package/node_modules/@maka/storage/dist/agent-run-store.js +18 -5
  146. package/node_modules/@maka/storage/dist/codex-session-adapter.js +14 -0
  147. package/node_modules/@maka/storage/dist/execution-stores.js +5 -0
  148. package/node_modules/@maka/storage/dist/interaction-store.js +13 -1
  149. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +9 -3
  150. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +49 -19
  151. package/node_modules/@maka/storage/dist/runtime-policy/oauth-login-receipt-document.js +3 -1
  152. package/node_modules/@maka/storage/dist/runtime-policy/onboarding-transaction.js +13 -5
  153. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +4 -2
  154. package/node_modules/@maka/storage/dist/session-store.js +39 -6
  155. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +16 -9
  156. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +30 -3
  157. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +117 -0
  158. package/node_modules/@maka/storage/dist/sqlite-usage-store.js +29 -6
  159. package/node_modules/@maka/storage/dist/usage-stores.js +3 -1
  160. package/package.json +1 -1
package/dist/cli-core.js CHANGED
@@ -131,7 +131,6 @@ function helpText(cliCommand) {
131
131
  ` ${cliCommand} runtime-host profile set --id <id> --name <name> --tls-url <wss-url> --expected-root <root-id> [--credential-env <name>]`,
132
132
  ` ${cliCommand} runtime-host profile set --id <id> --name <name> --ssh-destination <user@host> --ssh-remote-port <port> --expected-root <root-id> [--ssh-port <port>] [--credential-env <name>]`,
133
133
  ` ${cliCommand} runtime-host profile set --id <id> --name <name> --plaintext-url <ws-url> --acknowledge-plaintext --expected-root <root-id> [--credential-env <name>]`,
134
- ` ${cliCommand} runtime-host profile set --id <id> --name <name> --peer-id <peer-id> --peer-route <multiaddr> --expected-root <root-id> [--credential-env <name>]`,
135
134
  ` ${cliCommand} runtime-host profile remove --id <id>`,
136
135
  ` ${cliCommand} runtime-host capability-provider serve --url <ws-url> --mcp-config <path> --expected-root <root-id>`,
137
136
  '',
@@ -614,6 +614,9 @@ export function applyMakaSessionEventToTranscript(state, event) {
614
614
  case 'user_question_request':
615
615
  enqueuePendingInteraction(state, event);
616
616
  break;
617
+ case 'form_request':
618
+ enqueuePendingInteraction(state, event);
619
+ break;
617
620
  case 'sandbox_boundary_decision_ack':
618
621
  {
619
622
  const request = findPendingInteraction(state, event.requestId);
@@ -630,6 +633,9 @@ export function applyMakaSessionEventToTranscript(state, event) {
630
633
  case 'user_question_answer_ack':
631
634
  completePendingInteraction(state, event.requestId);
632
635
  break;
636
+ case 'form_answer_ack':
637
+ completePendingInteraction(state, event.requestId);
638
+ break;
633
639
  case 'plan_submitted':
634
640
  state.entries.push({
635
641
  kind: 'notice',
@@ -937,6 +943,45 @@ function systemNoteText(message) {
937
943
  return 'Context compacted to keep this task within the model window.';
938
944
  case 'context_compaction_failed_open':
939
945
  return 'Context summary failed; the session continued without a new summary.';
946
+ case 'context_provider_dropping': {
947
+ const data = message.data;
948
+ const used = typeof data?.inputTokens === 'number' ? data.inputTokens : undefined;
949
+ const prior = typeof data?.priorInputTokens === 'number' ? data.priorInputTokens : undefined;
950
+ if (used === undefined || prior === undefined) {
951
+ return 'The provider is dropping or rewriting context: content was appended but its reported usage did not grow. Declare a context window for this model so Maka compacts first.';
952
+ }
953
+ return `The provider is dropping or rewriting context: content was appended, and it counted ${used} input tokens against ${prior} before, which is no growth. Declare a context window for this model so Maka compacts first.`;
954
+ }
955
+ case 'context_overflow_after_compaction':
956
+ return 'History was compacted and the provider still called this request too large. What remains also carries the system prompt, the tool schemas, the summary and the recent tail; shortening this message is the part you control.';
957
+ case 'context_reported_window_exceeded': {
958
+ const data = message.data;
959
+ const used = typeof data?.usedTokens === 'number' ? data.usedTokens : undefined;
960
+ const reported = typeof data?.reportedContextWindow === 'number' ? data.reportedContextWindow : undefined;
961
+ if (used === undefined || reported === undefined) {
962
+ return 'This exchange ran past the context window this model reports, and the provider accepted it anyway.';
963
+ }
964
+ return `This exchange used about ${used} tokens, past the ${reported} this model reports, and the provider accepted it without complaint. Nothing is declared, so Maka does not compact on its own; declare a context window to have it compact first.`;
965
+ }
966
+ case 'context_window_overrun': {
967
+ const data = message.data;
968
+ const used = typeof data?.usedTokens === 'number' ? data.usedTokens : undefined;
969
+ const declared = typeof data?.declaredContextWindow === 'number' ? data.declaredContextWindow : undefined;
970
+ if (used === undefined || declared === undefined) {
971
+ return 'This exchange ran past the context window declared for this model.';
972
+ }
973
+ return `This exchange used about ${used} tokens against the declared window of ${declared}: the reply needed more room than was left. Maka compacts before the next request; raise the window if the replies should stay whole.`;
974
+ }
975
+ case 'context_window_suggestion': {
976
+ const data = message.data;
977
+ const tokens = typeof data?.suggestedContextWindow === 'number' ? data.suggestedContextWindow : undefined;
978
+ const declared = typeof data?.declaredContextWindow === 'number' ? data.declaredContextWindow : undefined;
979
+ if (tokens === undefined)
980
+ return 'The provider rejected this request as too large.';
981
+ return declared === undefined
982
+ ? `The provider rejected this request. No context window is declared for this model; the last accepted request was about ${tokens} tokens — declare that as the window so Maka compacts first.`
983
+ : `The provider rejected this request at about ${tokens} tokens, below the declared window of ${declared}. The declaration is likely larger than the provider's window; consider lowering it to ${tokens}.`;
984
+ }
940
985
  case 'step_limit':
941
986
  return STEP_LIMIT_NOTICE_TEXT;
942
987
  case 'error':
@@ -1015,6 +1060,9 @@ export function activeUserQuestionRequest(state) {
1015
1060
  ? state.pendingInteraction
1016
1061
  : undefined;
1017
1062
  }
1063
+ export function activeFormRequest(state) {
1064
+ return state.pendingInteraction?.type === 'form_request' ? state.pendingInteraction : undefined;
1065
+ }
1018
1066
  function enqueuePendingInteraction(state, request) {
1019
1067
  if (findPendingInteraction(state, request.requestId))
1020
1068
  return;
@@ -0,0 +1,497 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { Editor, Key, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
20
+ import { isInteractionFormFieldValueValid, } from '@maka/core/interaction';
21
+ import { sanitizeUnicodeText } from '@maka/core/text-sanitize';
22
+ import { defineUiMessageCatalog, formatUiMessage, resolveUiMessageCatalog, } from '@maka/core/ui-locale';
23
+ import { ansi, editorTheme, stripAnsi } from './tui-ansi.js';
24
+ import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
25
+ const FORM_COPY = resolveUiMessageCatalog(defineUiMessageCatalog()(TUI_COPY_RESOURCES['form-interaction']));
26
+ const FORM_REVIEW_MAX_VISIBLE_FIELDS = 8;
27
+ const FORM_CHOICE_MAX_VISIBLE_OPTIONS = 10;
28
+ export function createTuiFormDrafts(fields) {
29
+ return fields.map((field) => {
30
+ const included = field.required || field.default !== undefined;
31
+ if (field.kind === 'boolean')
32
+ return { included, value: field.default ?? false };
33
+ if (field.kind === 'multi_select')
34
+ return { included, value: [...(field.default ?? [])] };
35
+ if (field.kind === 'number' || field.kind === 'integer') {
36
+ return { included, value: field.default === undefined ? '' : String(field.default) };
37
+ }
38
+ return { included, value: field.default ?? '' };
39
+ });
40
+ }
41
+ export function buildTuiFormResponse(request, drafts) {
42
+ if (drafts.length !== request.fields.length)
43
+ return null;
44
+ const entries = [];
45
+ for (const [index, field] of request.fields.entries()) {
46
+ const draft = drafts[index];
47
+ if (!draft)
48
+ return null;
49
+ if (!draft.included) {
50
+ if (field.required)
51
+ return null;
52
+ continue;
53
+ }
54
+ const value = draftValue(field, draft);
55
+ if (!isInteractionFormFieldValueValid(field, value))
56
+ return null;
57
+ entries.push([field.name, value]);
58
+ }
59
+ return {
60
+ requestId: request.requestId,
61
+ action: 'accept',
62
+ values: Object.fromEntries(entries),
63
+ };
64
+ }
65
+ export class FormInteractionOverlay {
66
+ input;
67
+ #copy;
68
+ #editor;
69
+ #drafts;
70
+ #activeIndex = 0;
71
+ #mode = { kind: 'review' };
72
+ #invalid = new Set();
73
+ #submitting = false;
74
+ constructor(tui, input) {
75
+ this.input = input;
76
+ this.#copy = FORM_COPY[input.locale];
77
+ this.#drafts =
78
+ input.initialDrafts?.length === input.request.fields.length
79
+ ? cloneTuiFormDrafts(input.initialDrafts)
80
+ : createTuiFormDrafts(input.request.fields);
81
+ this.#editor = new Editor(tui, editorTheme(), { paddingX: 0 });
82
+ this.#editor.onChange = (value) => {
83
+ if (this.#mode.kind !== 'text')
84
+ return;
85
+ this.#replaceDraft(this.#mode.index, { value });
86
+ };
87
+ }
88
+ invalidate() {
89
+ this.#editor.invalidate();
90
+ }
91
+ setSubmissionFailed() {
92
+ this.#submitting = false;
93
+ }
94
+ snapshotDrafts() {
95
+ return cloneTuiFormDrafts(this.#drafts);
96
+ }
97
+ handleInput(data) {
98
+ if (this.#submitting)
99
+ return;
100
+ if (this.#mode.kind === 'review') {
101
+ this.#handleReviewInput(data);
102
+ }
103
+ else if (this.#mode.kind === 'text') {
104
+ if (matchesKey(data, Key.escape)) {
105
+ this.#mode = { kind: 'review' };
106
+ }
107
+ else if ((matchesKey(data, Key.enter) || matchesKey(data, Key.return)) &&
108
+ !isKeyRepeat(data)) {
109
+ this.#replaceDraft(this.#mode.index, { value: this.#editor.getText() });
110
+ this.#mode = { kind: 'review' };
111
+ }
112
+ else {
113
+ this.#editor.handleInput(data);
114
+ }
115
+ }
116
+ else if (this.#mode.kind === 'choice') {
117
+ this.#handleChoiceInput(data, this.#mode);
118
+ }
119
+ else {
120
+ this.#handleMultiInput(data, this.#mode);
121
+ }
122
+ }
123
+ render(width) {
124
+ const safeWidth = Math.max(1, width);
125
+ const lines = this.#renderHeader(safeWidth);
126
+ if (this.#mode.kind === 'review')
127
+ lines.push(...this.#renderReview(safeWidth));
128
+ else if (this.#mode.kind === 'text')
129
+ lines.push(...this.#renderTextEditor(this.#mode.index, safeWidth));
130
+ else if (this.#mode.kind === 'choice')
131
+ lines.push(...this.#renderChoice(this.#mode, safeWidth));
132
+ else
133
+ lines.push(...this.#renderMulti(this.#mode, safeWidth));
134
+ lines.push(padLine(ansi.accent('-'.repeat(safeWidth)), safeWidth));
135
+ return lines;
136
+ }
137
+ #handleReviewInput(data) {
138
+ const fields = this.input.request.fields;
139
+ if (matchesKey(data, Key.escape)) {
140
+ this.#respond({ requestId: this.input.request.requestId, action: 'cancel' });
141
+ return;
142
+ }
143
+ if (data === 'd' || data === 'D') {
144
+ this.#respond({ requestId: this.input.request.requestId, action: 'decline' });
145
+ return;
146
+ }
147
+ if (data === 's' || data === 'S') {
148
+ this.#submit();
149
+ return;
150
+ }
151
+ if (fields.length === 0)
152
+ return;
153
+ if (matchesKey(data, Key.up)) {
154
+ this.#activeIndex = this.#activeIndex === 0 ? fields.length - 1 : this.#activeIndex - 1;
155
+ return;
156
+ }
157
+ if (matchesKey(data, Key.down)) {
158
+ this.#activeIndex = this.#activeIndex === fields.length - 1 ? 0 : this.#activeIndex + 1;
159
+ return;
160
+ }
161
+ const field = fields[this.#activeIndex];
162
+ const draft = this.#drafts[this.#activeIndex];
163
+ if (!field || !draft)
164
+ return;
165
+ if (matchesKey(data, Key.space) && !field.required) {
166
+ this.#replaceDraft(this.#activeIndex, { included: !draft.included });
167
+ return;
168
+ }
169
+ if ((matchesKey(data, Key.enter) || matchesKey(data, Key.return)) && !isKeyRepeat(data)) {
170
+ if (!draft.included)
171
+ this.#replaceDraft(this.#activeIndex, { included: true });
172
+ this.#openEditor(field, this.#activeIndex);
173
+ }
174
+ }
175
+ #handleChoiceInput(data, mode) {
176
+ if (matchesKey(data, Key.escape)) {
177
+ this.#mode = { kind: 'review' };
178
+ return;
179
+ }
180
+ const field = this.input.request.fields[mode.index];
181
+ if (!field || (field.kind !== 'boolean' && field.kind !== 'single_select'))
182
+ return;
183
+ const count = field.kind === 'boolean' ? 2 : field.options.length;
184
+ if (count === 0)
185
+ return;
186
+ if (matchesKey(data, Key.up))
187
+ mode.optionIndex = mode.optionIndex === 0 ? count - 1 : mode.optionIndex - 1;
188
+ else if (matchesKey(data, Key.down))
189
+ mode.optionIndex = mode.optionIndex === count - 1 ? 0 : mode.optionIndex + 1;
190
+ else if ((matchesKey(data, Key.enter) || matchesKey(data, Key.return)) && !isKeyRepeat(data)) {
191
+ const value = field.kind === 'boolean' ? mode.optionIndex === 0 : field.options[mode.optionIndex]?.value;
192
+ if (value !== undefined)
193
+ this.#replaceDraft(mode.index, { included: true, value });
194
+ this.#mode = { kind: 'review' };
195
+ }
196
+ }
197
+ #handleMultiInput(data, mode) {
198
+ if (matchesKey(data, Key.escape) ||
199
+ matchesKey(data, Key.enter) ||
200
+ matchesKey(data, Key.return)) {
201
+ if (!isKeyRepeat(data))
202
+ this.#mode = { kind: 'review' };
203
+ return;
204
+ }
205
+ const field = this.input.request.fields[mode.index];
206
+ const draft = this.#drafts[mode.index];
207
+ if (!field || field.kind !== 'multi_select' || !draft || !Array.isArray(draft.value))
208
+ return;
209
+ if (field.options.length === 0)
210
+ return;
211
+ if (matchesKey(data, Key.up))
212
+ mode.optionIndex = mode.optionIndex === 0 ? field.options.length - 1 : mode.optionIndex - 1;
213
+ else if (matchesKey(data, Key.down))
214
+ mode.optionIndex = mode.optionIndex === field.options.length - 1 ? 0 : mode.optionIndex + 1;
215
+ else if (matchesKey(data, Key.space)) {
216
+ const value = field.options[mode.optionIndex]?.value;
217
+ if (value === undefined)
218
+ return;
219
+ const selected = draft.value.includes(value)
220
+ ? draft.value.filter((candidate) => candidate !== value)
221
+ : [...draft.value, value];
222
+ this.#replaceDraft(mode.index, { included: true, value: selected });
223
+ }
224
+ }
225
+ #openEditor(field, index) {
226
+ const draft = this.#drafts[index];
227
+ if (!draft)
228
+ return;
229
+ if (field.kind === 'boolean') {
230
+ this.#mode = { kind: 'choice', index, optionIndex: draft.value === true ? 0 : 1 };
231
+ }
232
+ else if (field.kind === 'single_select') {
233
+ this.#mode = {
234
+ kind: 'choice',
235
+ index,
236
+ optionIndex: Math.max(0, field.options.findIndex((option) => option.value === draft.value)),
237
+ };
238
+ }
239
+ else if (field.kind === 'multi_select') {
240
+ this.#mode = { kind: 'multi', index, optionIndex: 0 };
241
+ }
242
+ else {
243
+ this.#editor.setText(typeof draft.value === 'string' ? draft.value : '');
244
+ this.#mode = { kind: 'text', index };
245
+ }
246
+ }
247
+ #submit() {
248
+ const response = buildTuiFormResponse(this.input.request, this.#drafts);
249
+ if (response) {
250
+ this.#respond(response);
251
+ return;
252
+ }
253
+ this.#invalid = new Set(this.input.request.fields.flatMap((field, index) => {
254
+ const draft = this.#drafts[index];
255
+ if (!draft || (!draft.included && field.required))
256
+ return [index];
257
+ if (!draft.included)
258
+ return [];
259
+ return isInteractionFormFieldValueValid(field, draftValue(field, draft)) ? [] : [index];
260
+ }));
261
+ const first = this.#invalid.values().next().value;
262
+ if (typeof first === 'number')
263
+ this.#activeIndex = first;
264
+ }
265
+ #respond(response) {
266
+ this.#submitting = true;
267
+ this.input.onRespond(response);
268
+ }
269
+ #replaceDraft(index, patch) {
270
+ const current = this.#drafts[index];
271
+ if (!current)
272
+ return;
273
+ this.#drafts = this.#drafts.map((draft, candidate) => candidate === index ? { ...current, ...patch } : draft);
274
+ this.#invalid.delete(index);
275
+ }
276
+ #renderHeader(width) {
277
+ const requester = this.input.request.requester;
278
+ const detail = requester.source
279
+ ? `${safeDisplay(requester.name)} · ${safeDisplay(requester.source)}`
280
+ : safeDisplay(requester.name);
281
+ const provenance = formatUiMessage(this.#copy.requestedBy, { detail }, this.input.locale);
282
+ return [
283
+ padLine(ansi.bold(safeDisplay(this.input.request.message)), width),
284
+ padLine(ansi.dim(provenance), width),
285
+ padLine(ansi.red(this.#copy.sensitiveWarning), width),
286
+ padLine('', width),
287
+ ];
288
+ }
289
+ #renderReview(width) {
290
+ const lines = [];
291
+ const window = visibleWindow(this.input.request.fields.length, this.#activeIndex, FORM_REVIEW_MAX_VISIBLE_FIELDS);
292
+ if (window.start > 0)
293
+ lines.push(padLine(ansi.dim(` … ↑ ${window.start}`), width));
294
+ this.input.request.fields.slice(window.start, window.end).forEach((field, offset) => {
295
+ const index = window.start + offset;
296
+ const draft = this.#drafts[index];
297
+ if (!draft)
298
+ return;
299
+ const requirement = field.required ? this.#copy.required : this.#copy.optional;
300
+ const prefix = index === this.#activeIndex ? '→ ' : ' ';
301
+ const summary = this.#summary(field, draft);
302
+ const invalid = this.#invalid.has(index);
303
+ const row = `${prefix}${safeDisplay(field.label)} (${requirement}): ${summary}${invalid ? ` · ${this.#copy.invalid}` : ''}`;
304
+ lines.push(formatReviewRow(invalid ? ansi.red(row) : row, index === this.#activeIndex, width));
305
+ if (index === this.#activeIndex && field.description) {
306
+ lines.push(padLine(` ${ansi.dim(safeDisplay(field.description))}`, width));
307
+ }
308
+ if (index === this.#activeIndex) {
309
+ const constraint = this.#constraint(field);
310
+ if (constraint)
311
+ lines.push(padLine(` ${ansi.dim(constraint)}`, width));
312
+ }
313
+ });
314
+ if (window.end < this.input.request.fields.length) {
315
+ lines.push(padLine(ansi.dim(` … ↓ ${this.input.request.fields.length - window.end}`), width));
316
+ }
317
+ if (this.input.request.fields.length === 0)
318
+ lines.push(padLine(ansi.dim('(no fields)'), width));
319
+ lines.push(padLine('', width));
320
+ lines.push(padLine(ansi.dim(this.#copy.reviewHint), width));
321
+ return lines;
322
+ }
323
+ #renderTextEditor(index, width) {
324
+ const field = this.input.request.fields[index];
325
+ if (!field)
326
+ return [];
327
+ this.#editor.focused = true;
328
+ return [
329
+ padLine(`${safeDisplay(field.label)} (${field.required ? this.#copy.required : this.#copy.optional})`, width),
330
+ ...this.#fieldDetails(field, width),
331
+ ...this.#editor.render(width),
332
+ padLine(ansi.dim(this.#copy.textHint), width),
333
+ ];
334
+ }
335
+ #renderChoice(mode, width) {
336
+ const field = this.input.request.fields[mode.index];
337
+ if (!field || (field.kind !== 'boolean' && field.kind !== 'single_select'))
338
+ return [];
339
+ const options = field.kind === 'boolean'
340
+ ? [this.#copy.trueValue, this.#copy.falseValue]
341
+ : field.options.map((option) => safeDisplay(option.label));
342
+ const window = visibleWindow(options.length, mode.optionIndex, FORM_CHOICE_MAX_VISIBLE_OPTIONS);
343
+ return [
344
+ padLine(safeDisplay(field.label), width),
345
+ ...this.#fieldDetails(field, width),
346
+ ...(window.start > 0 ? [padLine(ansi.dim(` … ↑ ${window.start}`), width)] : []),
347
+ ...options.slice(window.start, window.end).map((label, offset) => {
348
+ const index = window.start + offset;
349
+ return formatReviewRow(`${index === mode.optionIndex ? '→ ' : ' '}${label}`, index === mode.optionIndex, width);
350
+ }),
351
+ ...(window.end < options.length
352
+ ? [padLine(ansi.dim(` … ↓ ${options.length - window.end}`), width)]
353
+ : []),
354
+ padLine(ansi.dim(this.#copy.choiceHint), width),
355
+ ];
356
+ }
357
+ #renderMulti(mode, width) {
358
+ const field = this.input.request.fields[mode.index];
359
+ const draft = this.#drafts[mode.index];
360
+ if (!field || field.kind !== 'multi_select' || !draft || !Array.isArray(draft.value))
361
+ return [];
362
+ const selected = draft.value;
363
+ const window = visibleWindow(field.options.length, mode.optionIndex, FORM_CHOICE_MAX_VISIBLE_OPTIONS);
364
+ return [
365
+ padLine(safeDisplay(field.label), width),
366
+ ...this.#fieldDetails(field, width),
367
+ ...(window.start > 0 ? [padLine(ansi.dim(` … ↑ ${window.start}`), width)] : []),
368
+ ...field.options.slice(window.start, window.end).map((option, offset) => {
369
+ const index = window.start + offset;
370
+ const row = `${index === mode.optionIndex ? '→ ' : ' '}[${selected.includes(option.value) ? 'x' : ' '}] ${safeDisplay(option.label)}`;
371
+ return formatReviewRow(row, index === mode.optionIndex, width);
372
+ }),
373
+ ...(window.end < field.options.length
374
+ ? [padLine(ansi.dim(` … ↓ ${field.options.length - window.end}`), width)]
375
+ : []),
376
+ padLine(ansi.dim(this.#copy.multiHint), width),
377
+ ];
378
+ }
379
+ #summary(field, draft) {
380
+ if (!draft.included)
381
+ return ansi.dim(this.#copy.omitted);
382
+ if (field.kind === 'boolean')
383
+ return draft.value === true ? this.#copy.trueValue : this.#copy.falseValue;
384
+ if (field.kind === 'single_select') {
385
+ const option = field.options.find((candidate) => candidate.value === draft.value);
386
+ return option ? safeDisplay(option.label) : ansi.dim(this.#copy.empty);
387
+ }
388
+ if (field.kind === 'multi_select' && Array.isArray(draft.value)) {
389
+ return formatUiMessage(this.#copy.selectedCount, { count: draft.value.length }, this.input.locale);
390
+ }
391
+ return typeof draft.value === 'string' && draft.value.length > 0
392
+ ? safeDisplay(draft.value)
393
+ : ansi.dim(this.#copy.empty);
394
+ }
395
+ #fieldDetails(field, width) {
396
+ const details = field.description ? [safeDisplay(field.description)] : [];
397
+ const constraint = this.#constraint(field);
398
+ if (constraint)
399
+ details.push(constraint);
400
+ return details.map((detail) => padLine(ansi.dim(detail), width));
401
+ }
402
+ #constraint(field) {
403
+ const constraints = [];
404
+ if (field.kind === 'string') {
405
+ if (field.minLength !== undefined && field.maxLength !== undefined) {
406
+ constraints.push(formatUiMessage(this.#copy.lengthRange, {
407
+ minimum: field.minLength,
408
+ maximum: field.maxLength,
409
+ }, this.input.locale));
410
+ }
411
+ else if (field.minLength !== undefined) {
412
+ constraints.push(formatUiMessage(this.#copy.minimumLength, {
413
+ minimum: field.minLength,
414
+ }, this.input.locale));
415
+ }
416
+ else if (field.maxLength !== undefined) {
417
+ constraints.push(formatUiMessage(this.#copy.maximumLength, {
418
+ maximum: field.maxLength,
419
+ }, this.input.locale));
420
+ }
421
+ if (field.format !== undefined) {
422
+ constraints.push(formatUiMessage(this.#copy.format, {
423
+ format: field.format,
424
+ }, this.input.locale));
425
+ }
426
+ }
427
+ else if (field.kind === 'number' || field.kind === 'integer') {
428
+ if (field.minimum !== undefined && field.maximum !== undefined) {
429
+ constraints.push(formatUiMessage(this.#copy.valueRange, {
430
+ minimum: field.minimum,
431
+ maximum: field.maximum,
432
+ }, this.input.locale));
433
+ }
434
+ else if (field.minimum !== undefined) {
435
+ constraints.push(formatUiMessage(this.#copy.minimumValue, {
436
+ minimum: field.minimum,
437
+ }, this.input.locale));
438
+ }
439
+ else if (field.maximum !== undefined) {
440
+ constraints.push(formatUiMessage(this.#copy.maximumValue, {
441
+ maximum: field.maximum,
442
+ }, this.input.locale));
443
+ }
444
+ }
445
+ else if (field.kind === 'multi_select') {
446
+ if (field.minItems !== undefined && field.maxItems !== undefined) {
447
+ constraints.push(formatUiMessage(this.#copy.itemRange, {
448
+ minimum: field.minItems,
449
+ maximum: field.maxItems,
450
+ }, this.input.locale));
451
+ }
452
+ else if (field.minItems !== undefined) {
453
+ constraints.push(formatUiMessage(this.#copy.minimumItems, {
454
+ minimum: field.minItems,
455
+ }, this.input.locale));
456
+ }
457
+ else if (field.maxItems !== undefined) {
458
+ constraints.push(formatUiMessage(this.#copy.maximumItems, {
459
+ maximum: field.maxItems,
460
+ }, this.input.locale));
461
+ }
462
+ }
463
+ return constraints.length === 0 ? undefined : constraints.join(' · ');
464
+ }
465
+ }
466
+ function cloneTuiFormDrafts(drafts) {
467
+ return drafts.map((draft) => ({
468
+ included: draft.included,
469
+ value: Array.isArray(draft.value) ? [...draft.value] : draft.value,
470
+ }));
471
+ }
472
+ function draftValue(field, draft) {
473
+ if (field.kind === 'number' || field.kind === 'integer') {
474
+ return typeof draft.value === 'string' && draft.value.trim() !== ''
475
+ ? Number(draft.value)
476
+ : Number.NaN;
477
+ }
478
+ return draft.value;
479
+ }
480
+ function safeDisplay(value) {
481
+ return sanitizeUnicodeText(stripAnsi(value), { maxCodePoints: 1_024 });
482
+ }
483
+ function formatReviewRow(text, active, width) {
484
+ const padded = padLine(text, width);
485
+ return active ? ansi.reverse(padded) : padded;
486
+ }
487
+ function padLine(text, width) {
488
+ const safeWidth = Math.max(1, width);
489
+ const trimmed = visibleWidth(text) > safeWidth ? truncateToWidth(text, safeWidth, '') : text;
490
+ return `${trimmed}${' '.repeat(Math.max(0, safeWidth - visibleWidth(trimmed)))}`;
491
+ }
492
+ function visibleWindow(length, activeIndex, limit) {
493
+ if (length <= limit)
494
+ return { start: 0, end: length };
495
+ const start = Math.min(Math.max(0, activeIndex - Math.floor(limit / 2)), Math.max(0, length - limit));
496
+ return { start, end: Math.min(length, start + limit) };
497
+ }