@dexto/tui 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/agent-backend.cjs +16 -2
  2. package/dist/agent-backend.d.ts +5 -2
  3. package/dist/agent-backend.d.ts.map +1 -1
  4. package/dist/agent-backend.js +15 -2
  5. package/dist/agent-backend.test.cjs +28 -2
  6. package/dist/agent-backend.test.js +28 -2
  7. package/dist/components/ApprovalPrompt.cjs +6 -5
  8. package/dist/components/ApprovalPrompt.d.ts +1 -1
  9. package/dist/components/ApprovalPrompt.d.ts.map +1 -1
  10. package/dist/components/ApprovalPrompt.js +6 -5
  11. package/dist/components/Footer.cjs +3 -2
  12. package/dist/components/Footer.d.ts.map +1 -1
  13. package/dist/components/Footer.js +2 -5
  14. package/dist/components/TextBufferInput.cjs +14 -1
  15. package/dist/components/TextBufferInput.d.ts +5 -1
  16. package/dist/components/TextBufferInput.d.ts.map +1 -1
  17. package/dist/components/TextBufferInput.js +14 -1
  18. package/dist/components/chat/QueuedMessagesDisplay.cjs +17 -8
  19. package/dist/components/chat/QueuedMessagesDisplay.d.ts +7 -1
  20. package/dist/components/chat/QueuedMessagesDisplay.d.ts.map +1 -1
  21. package/dist/components/chat/QueuedMessagesDisplay.js +16 -8
  22. package/dist/components/input/InputArea.cjs +4 -0
  23. package/dist/components/input/InputArea.d.ts +5 -1
  24. package/dist/components/input/InputArea.d.ts.map +1 -1
  25. package/dist/components/input/InputArea.js +4 -0
  26. package/dist/components/modes/AlternateBufferCLI.cjs +20 -1
  27. package/dist/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  28. package/dist/components/modes/AlternateBufferCLI.js +21 -2
  29. package/dist/components/modes/StaticCLI.cjs +20 -1
  30. package/dist/components/modes/StaticCLI.d.ts.map +1 -1
  31. package/dist/components/modes/StaticCLI.js +21 -2
  32. package/dist/components/overlays/ApiKeyInput.d.ts +1 -1
  33. package/dist/components/overlays/ApiKeyInput.d.ts.map +1 -1
  34. package/dist/components/overlays/CustomModelWizard.d.ts.map +1 -1
  35. package/dist/components/overlays/LoginOverlay.cjs +2 -10
  36. package/dist/components/overlays/LoginOverlay.d.ts.map +1 -1
  37. package/dist/components/overlays/LoginOverlay.js +3 -11
  38. package/dist/components/overlays/ModelSelectorRefactored.cjs +4 -3
  39. package/dist/components/overlays/ModelSelectorRefactored.d.ts +1 -1
  40. package/dist/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  41. package/dist/components/overlays/ModelSelectorRefactored.js +1 -2
  42. package/dist/components/overlays/ReasoningOverlay.cjs +3 -3
  43. package/dist/components/overlays/ReasoningOverlay.js +1 -1
  44. package/dist/components/overlays/custom-model-wizard/provider-config.cjs +4 -3
  45. package/dist/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -1
  46. package/dist/components/overlays/custom-model-wizard/provider-config.js +2 -4
  47. package/dist/containers/InputContainer.cjs +121 -20
  48. package/dist/containers/InputContainer.d.ts +6 -2
  49. package/dist/containers/InputContainer.d.ts.map +1 -1
  50. package/dist/containers/InputContainer.js +120 -19
  51. package/dist/containers/OverlayContainer.cjs +6 -5
  52. package/dist/containers/OverlayContainer.d.ts.map +1 -1
  53. package/dist/containers/OverlayContainer.js +2 -7
  54. package/dist/hooks/useAgentEvents.cjs +29 -6
  55. package/dist/hooks/useAgentEvents.d.ts +3 -2
  56. package/dist/hooks/useAgentEvents.d.ts.map +1 -1
  57. package/dist/hooks/useAgentEvents.js +29 -9
  58. package/dist/hooks/useCLIState.cjs +12 -5
  59. package/dist/hooks/useCLIState.d.ts +2 -0
  60. package/dist/hooks/useCLIState.d.ts.map +1 -1
  61. package/dist/hooks/useCLIState.js +12 -5
  62. package/dist/hooks/useInputOrchestrator.cjs +15 -14
  63. package/dist/hooks/useInputOrchestrator.d.ts +6 -6
  64. package/dist/hooks/useInputOrchestrator.d.ts.map +1 -1
  65. package/dist/hooks/useInputOrchestrator.js +15 -14
  66. package/dist/host/index.cjs +6 -6
  67. package/dist/host/index.d.ts +9 -18
  68. package/dist/host/index.d.ts.map +1 -1
  69. package/dist/host/index.js +5 -5
  70. package/dist/host/index.test.cjs +47 -0
  71. package/dist/host/index.test.d.ts +2 -0
  72. package/dist/host/index.test.d.ts.map +1 -0
  73. package/dist/host/index.test.js +50 -0
  74. package/dist/index.d.cts +11 -15
  75. package/dist/interactive-commands/command-parser.cjs +1 -0
  76. package/dist/interactive-commands/command-parser.d.ts.map +1 -1
  77. package/dist/interactive-commands/command-parser.js +1 -0
  78. package/dist/interactive-commands/commands.cjs +3 -0
  79. package/dist/interactive-commands/commands.d.ts.map +1 -1
  80. package/dist/interactive-commands/commands.js +3 -0
  81. package/dist/interactive-commands/commands.test.cjs +42 -0
  82. package/dist/interactive-commands/commands.test.js +42 -0
  83. package/dist/interactive-commands/prompt-commands.cjs +4 -66
  84. package/dist/interactive-commands/prompt-commands.d.ts +1 -2
  85. package/dist/interactive-commands/prompt-commands.d.ts.map +1 -1
  86. package/dist/interactive-commands/prompt-commands.js +4 -66
  87. package/dist/interactive-commands/skill-commands.cjs +73 -0
  88. package/dist/interactive-commands/skill-commands.d.ts +9 -0
  89. package/dist/interactive-commands/skill-commands.d.ts.map +1 -0
  90. package/dist/interactive-commands/skill-commands.js +49 -0
  91. package/dist/services/processStream.cjs +23 -4
  92. package/dist/services/processStream.d.ts +3 -1
  93. package/dist/services/processStream.d.ts.map +1 -1
  94. package/dist/services/processStream.js +23 -4
  95. package/dist/services/processStream.test.cjs +52 -2
  96. package/dist/services/processStream.test.js +52 -2
  97. package/dist/state/initialState.cjs +2 -1
  98. package/dist/state/initialState.d.ts.map +1 -1
  99. package/dist/state/initialState.js +2 -1
  100. package/dist/state/reducer.cjs +10 -5
  101. package/dist/state/reducer.d.ts.map +1 -1
  102. package/dist/state/reducer.js +10 -5
  103. package/dist/state/types.d.ts +4 -1
  104. package/dist/state/types.d.ts.map +1 -1
  105. package/dist/utils/chatgpt-rate-limit.cjs +4 -4
  106. package/dist/utils/chatgpt-rate-limit.d.ts.map +1 -1
  107. package/dist/utils/chatgpt-rate-limit.js +1 -1
  108. package/dist/utils/llm-provider-display.d.ts +1 -1
  109. package/dist/utils/llm-provider-display.d.ts.map +1 -1
  110. package/dist/utils/messageFormatting.cjs +0 -23
  111. package/dist/utils/messageFormatting.d.ts +0 -13
  112. package/dist/utils/messageFormatting.d.ts.map +1 -1
  113. package/dist/utils/messageFormatting.js +0 -21
  114. package/dist/utils/queuedComposerContent.cjs +148 -0
  115. package/dist/utils/queuedComposerContent.d.ts +17 -0
  116. package/dist/utils/queuedComposerContent.d.ts.map +1 -0
  117. package/dist/utils/queuedComposerContent.js +123 -0
  118. package/dist/utils/queuedComposerContent.test.cjs +176 -0
  119. package/dist/utils/queuedComposerContent.test.d.ts +2 -0
  120. package/dist/utils/queuedComposerContent.test.d.ts.map +1 -0
  121. package/dist/utils/queuedComposerContent.test.js +175 -0
  122. package/package.json +5 -4
@@ -0,0 +1,123 @@
1
+ function imagePlaceholder(index) {
2
+ return `[Image ${index}]`;
3
+ }
4
+ function imagePlaceholdersIn(text) {
5
+ const seen = /* @__PURE__ */ new Set();
6
+ const placeholders = [];
7
+ for (const match of text.matchAll(/\[Image \d+\]/g)) {
8
+ const placeholder = match[0];
9
+ if (!seen.has(placeholder)) {
10
+ seen.add(placeholder);
11
+ placeholders.push(placeholder);
12
+ }
13
+ }
14
+ return placeholders;
15
+ }
16
+ function nextImagePlaceholder(usedPlaceholders) {
17
+ let index = 1;
18
+ while (usedPlaceholders.has(imagePlaceholder(index))) {
19
+ index += 1;
20
+ }
21
+ const placeholder = imagePlaceholder(index);
22
+ usedPlaceholders.add(placeholder);
23
+ return placeholder;
24
+ }
25
+ function imageDataForComposer(part) {
26
+ if (typeof part.image === "string") {
27
+ return part.image;
28
+ }
29
+ if (part.image instanceof URL) {
30
+ return part.image.toString();
31
+ }
32
+ if (part.image instanceof ArrayBuffer) {
33
+ return Buffer.from(new Uint8Array(part.image)).toString("base64");
34
+ }
35
+ return Buffer.from(part.image).toString("base64");
36
+ }
37
+ function textParts(content) {
38
+ return content.filter((part) => part.type === "text");
39
+ }
40
+ function imageParts(content) {
41
+ return content.filter((part) => part.type === "image");
42
+ }
43
+ function unsupportedParts(content) {
44
+ return content.filter((part) => part.type !== "text" && part.type !== "image");
45
+ }
46
+ function appendPlaceholderSegment(segments, placeholder) {
47
+ const previousSegment = segments[segments.length - 1];
48
+ if (previousSegment?.match(/^\[Image \d+\]( \[Image \d+\])*$/)) {
49
+ segments[segments.length - 1] = `${previousSegment} ${placeholder}`;
50
+ return;
51
+ }
52
+ segments.push(placeholder);
53
+ }
54
+ function composerTextAndPlaceholders(content) {
55
+ const text = textParts(content).map((part) => part.text).join("\n");
56
+ const existingPlaceholders = imagePlaceholdersIn(text);
57
+ const usedPlaceholders = new Set(existingPlaceholders);
58
+ const placeholders = [];
59
+ const segments = [];
60
+ let existingPlaceholderIndex = 0;
61
+ for (const part of content) {
62
+ if (part.type === "text") {
63
+ if (part.text.length > 0) {
64
+ segments.push(part.text);
65
+ }
66
+ continue;
67
+ }
68
+ if (part.type !== "image") {
69
+ continue;
70
+ }
71
+ const existingPlaceholder = existingPlaceholders[existingPlaceholderIndex];
72
+ if (existingPlaceholder) {
73
+ placeholders.push(existingPlaceholder);
74
+ existingPlaceholderIndex += 1;
75
+ continue;
76
+ }
77
+ const placeholder = nextImagePlaceholder(usedPlaceholders);
78
+ placeholders.push(placeholder);
79
+ appendPlaceholderSegment(segments, placeholder);
80
+ }
81
+ return { text: segments.join("\n"), placeholders };
82
+ }
83
+ function restoreQueuedContentForComposer(message) {
84
+ const unsupported = unsupportedParts(message.content);
85
+ if (unsupported.length > 0) {
86
+ return {
87
+ ok: false,
88
+ reason: "Queued input with non-image attachments cannot be edited in the terminal yet."
89
+ };
90
+ }
91
+ const images = imageParts(message.content);
92
+ const composer = composerTextAndPlaceholders(message.content);
93
+ return {
94
+ ok: true,
95
+ composer: {
96
+ text: composer.text,
97
+ images: images.map((part, index) => ({
98
+ id: `${message.id}-image-${index + 1}`,
99
+ data: imageDataForComposer(part),
100
+ mimeType: part.mimeType ?? "image/png",
101
+ placeholder: composer.placeholders[index] ?? imagePlaceholder(index + 1)
102
+ }))
103
+ }
104
+ };
105
+ }
106
+ function previewQueuedContent(content) {
107
+ const textWithImages = composerTextAndPlaceholders(content).text.replace(/\n/g, " ");
108
+ const markers = content.filter((part) => part.type !== "text" && part.type !== "image").map((part) => {
109
+ switch (part.type) {
110
+ case "file":
111
+ return part.filename ? `[file: ${part.filename}]` : "[file]";
112
+ case "resource":
113
+ return `[resource: ${part.name}]`;
114
+ case "ui-resource":
115
+ return "[ui resource]";
116
+ }
117
+ });
118
+ return [textWithImages, ...markers].filter(Boolean).join(" ") || "[attachment]";
119
+ }
120
+ export {
121
+ previewQueuedContent,
122
+ restoreQueuedContentForComposer
123
+ };
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_queuedComposerContent = require("./queuedComposerContent.js");
4
+ function queuedMessage(content) {
5
+ return {
6
+ id: "queued-1",
7
+ content,
8
+ queuedAt: 123,
9
+ kind: "default"
10
+ };
11
+ }
12
+ (0, import_vitest.describe)("queuedComposerContent", () => {
13
+ (0, import_vitest.it)("restores queued text-only content without adding attachments", () => {
14
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
15
+ queuedMessage([
16
+ { type: "text", text: "first line" },
17
+ { type: "text", text: "second line" }
18
+ ])
19
+ );
20
+ (0, import_vitest.expect)(result).toEqual({
21
+ ok: true,
22
+ composer: {
23
+ text: "first line\nsecond line",
24
+ images: []
25
+ }
26
+ });
27
+ });
28
+ (0, import_vitest.it)("restores queued text and image content into composer text plus pending images", () => {
29
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
30
+ queuedMessage([
31
+ { type: "text", text: "describe this [Image 1]" },
32
+ { type: "image", image: "base64-image", mimeType: "image/png" }
33
+ ])
34
+ );
35
+ (0, import_vitest.expect)(result).toEqual({
36
+ ok: true,
37
+ composer: {
38
+ text: "describe this [Image 1]",
39
+ images: [
40
+ {
41
+ id: "queued-1-image-1",
42
+ data: "base64-image",
43
+ mimeType: "image/png",
44
+ placeholder: "[Image 1]"
45
+ }
46
+ ]
47
+ }
48
+ });
49
+ });
50
+ (0, import_vitest.it)("adds image placeholders when queued text does not already contain them", () => {
51
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
52
+ queuedMessage([
53
+ { type: "text", text: "describe this" },
54
+ { type: "image", image: "first", mimeType: "image/png" },
55
+ { type: "image", image: "second", mimeType: "image/jpeg" }
56
+ ])
57
+ );
58
+ (0, import_vitest.expect)(result).toMatchObject({
59
+ ok: true,
60
+ composer: {
61
+ text: "describe this\n[Image 1] [Image 2]",
62
+ images: [
63
+ { data: "first", placeholder: "[Image 1]" },
64
+ { data: "second", placeholder: "[Image 2]" }
65
+ ]
66
+ }
67
+ });
68
+ });
69
+ (0, import_vitest.it)("adds missing image placeholders at the original content position", () => {
70
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
71
+ queuedMessage([
72
+ { type: "text", text: "before" },
73
+ { type: "image", image: "base64-image", mimeType: "image/png" },
74
+ { type: "text", text: "after" }
75
+ ])
76
+ );
77
+ (0, import_vitest.expect)(result).toMatchObject({
78
+ ok: true,
79
+ composer: {
80
+ text: "before\n[Image 1]\nafter",
81
+ images: [{ data: "base64-image", placeholder: "[Image 1]" }]
82
+ }
83
+ });
84
+ });
85
+ (0, import_vitest.it)("preserves existing image placeholder numbering from queued text", () => {
86
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
87
+ queuedMessage([
88
+ { type: "text", text: "describe this [Image 3]" },
89
+ { type: "image", image: "base64-image", mimeType: "image/png" }
90
+ ])
91
+ );
92
+ (0, import_vitest.expect)(result).toEqual({
93
+ ok: true,
94
+ composer: {
95
+ text: "describe this [Image 3]",
96
+ images: [
97
+ {
98
+ id: "queued-1-image-1",
99
+ data: "base64-image",
100
+ mimeType: "image/png",
101
+ placeholder: "[Image 3]"
102
+ }
103
+ ]
104
+ }
105
+ });
106
+ });
107
+ (0, import_vitest.it)("refuses terminal edit for unsupported attachment parts instead of dropping them", () => {
108
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
109
+ queuedMessage([
110
+ { type: "text", text: "read this file" },
111
+ {
112
+ type: "file",
113
+ data: "file-data",
114
+ mimeType: "text/plain",
115
+ filename: "notes.txt"
116
+ }
117
+ ])
118
+ );
119
+ (0, import_vitest.expect)(result).toEqual({
120
+ ok: false,
121
+ reason: "Queued input with non-image attachments cannot be edited in the terminal yet."
122
+ });
123
+ });
124
+ (0, import_vitest.it)("restores binary image payloads instead of dropping them", () => {
125
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
126
+ queuedMessage([
127
+ { type: "text", text: "describe this" },
128
+ { type: "image", image: new Uint8Array([1, 2]), mimeType: "image/png" }
129
+ ])
130
+ );
131
+ (0, import_vitest.expect)(result).toMatchObject({
132
+ ok: true,
133
+ composer: {
134
+ text: "describe this\n[Image 1]",
135
+ images: [{ data: "AQI=", placeholder: "[Image 1]" }]
136
+ }
137
+ });
138
+ });
139
+ (0, import_vitest.it)("restores ArrayBuffer, Buffer, and URL image payloads", () => {
140
+ const result = (0, import_queuedComposerContent.restoreQueuedContentForComposer)(
141
+ queuedMessage([
142
+ { type: "image", image: new Uint8Array([3, 4]).buffer, mimeType: "image/png" },
143
+ { type: "image", image: Buffer.from([5, 6]), mimeType: "image/png" },
144
+ { type: "image", image: new URL("https://example.com/image.png") }
145
+ ])
146
+ );
147
+ (0, import_vitest.expect)(result).toMatchObject({
148
+ ok: true,
149
+ composer: {
150
+ text: "[Image 1] [Image 2] [Image 3]",
151
+ images: [
152
+ { data: "AwQ=", placeholder: "[Image 1]" },
153
+ { data: "BQY=", placeholder: "[Image 2]" },
154
+ { data: "https://example.com/image.png", placeholder: "[Image 3]" }
155
+ ]
156
+ }
157
+ });
158
+ });
159
+ (0, import_vitest.it)("includes image placeholders and attachment markers in queued previews", () => {
160
+ (0, import_vitest.expect)(
161
+ (0, import_queuedComposerContent.previewQueuedContent)([
162
+ { type: "text", text: "look at these" },
163
+ { type: "image", image: "img", mimeType: "image/png" },
164
+ { type: "file", data: "file", mimeType: "text/plain", filename: "notes.txt" }
165
+ ])
166
+ ).toBe("look at these [Image 1] [file: notes.txt]");
167
+ });
168
+ (0, import_vitest.it)("does not duplicate image markers already present in queued previews", () => {
169
+ (0, import_vitest.expect)(
170
+ (0, import_queuedComposerContent.previewQueuedContent)([
171
+ { type: "text", text: "look at this [Image 1]" },
172
+ { type: "image", image: "img", mimeType: "image/png" }
173
+ ])
174
+ ).toBe("look at this [Image 1]");
175
+ });
176
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=queuedComposerContent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queuedComposerContent.test.d.ts","sourceRoot":"","sources":["../../src/utils/queuedComposerContent.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,175 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { previewQueuedContent, restoreQueuedContentForComposer } from "./queuedComposerContent.js";
3
+ function queuedMessage(content) {
4
+ return {
5
+ id: "queued-1",
6
+ content,
7
+ queuedAt: 123,
8
+ kind: "default"
9
+ };
10
+ }
11
+ describe("queuedComposerContent", () => {
12
+ it("restores queued text-only content without adding attachments", () => {
13
+ const result = restoreQueuedContentForComposer(
14
+ queuedMessage([
15
+ { type: "text", text: "first line" },
16
+ { type: "text", text: "second line" }
17
+ ])
18
+ );
19
+ expect(result).toEqual({
20
+ ok: true,
21
+ composer: {
22
+ text: "first line\nsecond line",
23
+ images: []
24
+ }
25
+ });
26
+ });
27
+ it("restores queued text and image content into composer text plus pending images", () => {
28
+ const result = restoreQueuedContentForComposer(
29
+ queuedMessage([
30
+ { type: "text", text: "describe this [Image 1]" },
31
+ { type: "image", image: "base64-image", mimeType: "image/png" }
32
+ ])
33
+ );
34
+ expect(result).toEqual({
35
+ ok: true,
36
+ composer: {
37
+ text: "describe this [Image 1]",
38
+ images: [
39
+ {
40
+ id: "queued-1-image-1",
41
+ data: "base64-image",
42
+ mimeType: "image/png",
43
+ placeholder: "[Image 1]"
44
+ }
45
+ ]
46
+ }
47
+ });
48
+ });
49
+ it("adds image placeholders when queued text does not already contain them", () => {
50
+ const result = restoreQueuedContentForComposer(
51
+ queuedMessage([
52
+ { type: "text", text: "describe this" },
53
+ { type: "image", image: "first", mimeType: "image/png" },
54
+ { type: "image", image: "second", mimeType: "image/jpeg" }
55
+ ])
56
+ );
57
+ expect(result).toMatchObject({
58
+ ok: true,
59
+ composer: {
60
+ text: "describe this\n[Image 1] [Image 2]",
61
+ images: [
62
+ { data: "first", placeholder: "[Image 1]" },
63
+ { data: "second", placeholder: "[Image 2]" }
64
+ ]
65
+ }
66
+ });
67
+ });
68
+ it("adds missing image placeholders at the original content position", () => {
69
+ const result = restoreQueuedContentForComposer(
70
+ queuedMessage([
71
+ { type: "text", text: "before" },
72
+ { type: "image", image: "base64-image", mimeType: "image/png" },
73
+ { type: "text", text: "after" }
74
+ ])
75
+ );
76
+ expect(result).toMatchObject({
77
+ ok: true,
78
+ composer: {
79
+ text: "before\n[Image 1]\nafter",
80
+ images: [{ data: "base64-image", placeholder: "[Image 1]" }]
81
+ }
82
+ });
83
+ });
84
+ it("preserves existing image placeholder numbering from queued text", () => {
85
+ const result = restoreQueuedContentForComposer(
86
+ queuedMessage([
87
+ { type: "text", text: "describe this [Image 3]" },
88
+ { type: "image", image: "base64-image", mimeType: "image/png" }
89
+ ])
90
+ );
91
+ expect(result).toEqual({
92
+ ok: true,
93
+ composer: {
94
+ text: "describe this [Image 3]",
95
+ images: [
96
+ {
97
+ id: "queued-1-image-1",
98
+ data: "base64-image",
99
+ mimeType: "image/png",
100
+ placeholder: "[Image 3]"
101
+ }
102
+ ]
103
+ }
104
+ });
105
+ });
106
+ it("refuses terminal edit for unsupported attachment parts instead of dropping them", () => {
107
+ const result = restoreQueuedContentForComposer(
108
+ queuedMessage([
109
+ { type: "text", text: "read this file" },
110
+ {
111
+ type: "file",
112
+ data: "file-data",
113
+ mimeType: "text/plain",
114
+ filename: "notes.txt"
115
+ }
116
+ ])
117
+ );
118
+ expect(result).toEqual({
119
+ ok: false,
120
+ reason: "Queued input with non-image attachments cannot be edited in the terminal yet."
121
+ });
122
+ });
123
+ it("restores binary image payloads instead of dropping them", () => {
124
+ const result = restoreQueuedContentForComposer(
125
+ queuedMessage([
126
+ { type: "text", text: "describe this" },
127
+ { type: "image", image: new Uint8Array([1, 2]), mimeType: "image/png" }
128
+ ])
129
+ );
130
+ expect(result).toMatchObject({
131
+ ok: true,
132
+ composer: {
133
+ text: "describe this\n[Image 1]",
134
+ images: [{ data: "AQI=", placeholder: "[Image 1]" }]
135
+ }
136
+ });
137
+ });
138
+ it("restores ArrayBuffer, Buffer, and URL image payloads", () => {
139
+ const result = restoreQueuedContentForComposer(
140
+ queuedMessage([
141
+ { type: "image", image: new Uint8Array([3, 4]).buffer, mimeType: "image/png" },
142
+ { type: "image", image: Buffer.from([5, 6]), mimeType: "image/png" },
143
+ { type: "image", image: new URL("https://example.com/image.png") }
144
+ ])
145
+ );
146
+ expect(result).toMatchObject({
147
+ ok: true,
148
+ composer: {
149
+ text: "[Image 1] [Image 2] [Image 3]",
150
+ images: [
151
+ { data: "AwQ=", placeholder: "[Image 1]" },
152
+ { data: "BQY=", placeholder: "[Image 2]" },
153
+ { data: "https://example.com/image.png", placeholder: "[Image 3]" }
154
+ ]
155
+ }
156
+ });
157
+ });
158
+ it("includes image placeholders and attachment markers in queued previews", () => {
159
+ expect(
160
+ previewQueuedContent([
161
+ { type: "text", text: "look at these" },
162
+ { type: "image", image: "img", mimeType: "image/png" },
163
+ { type: "file", data: "file", mimeType: "text/plain", filename: "notes.txt" }
164
+ ])
165
+ ).toBe("look at these [Image 1] [file: notes.txt]");
166
+ });
167
+ it("does not duplicate image markers already present in queued previews", () => {
168
+ expect(
169
+ previewQueuedContent([
170
+ { type: "text", text: "look at this [Image 1]" },
171
+ { type: "image", image: "img", mimeType: "image/png" }
172
+ ])
173
+ ).toBe("look at this [Image 1]");
174
+ });
175
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexto/tui",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "Interactive terminal UI for Dexto CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,9 +28,10 @@
28
28
  "string-width": "^8.1.0",
29
29
  "strip-ansi": "^7.1.2",
30
30
  "wrap-ansi": "^9.0.2",
31
- "@dexto/core": "1.7.2",
32
- "@dexto/registry": "1.7.2",
33
- "@dexto/agent-management": "1.7.2"
31
+ "@dexto/agent-management": "1.8.1",
32
+ "@dexto/core": "1.8.1",
33
+ "@dexto/llm": "1.8.1",
34
+ "@dexto/registry": "1.8.1"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/react": "^19.0.0",