gsd-pi 2.70.1-dev.ec24142 → 2.71.0-dev.06b86c6
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/README.md +24 -17
- package/dist/cli.js +12 -3
- package/dist/mcp-server.js +6 -6
- package/dist/provider-migrations.d.ts +10 -0
- package/dist/provider-migrations.js +12 -0
- package/dist/resource-loader.js +136 -13
- package/dist/resources/GSD-WORKFLOW.md +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
- package/dist/resources/extensions/gsd/commands/context.js +15 -6
- package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +16 -12
- package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
- package/dist/resources/extensions/gsd/error-classifier.js +1 -1
- package/dist/resources/extensions/gsd/file-lock.js +60 -0
- package/dist/resources/extensions/gsd/notification-store.js +21 -1
- package/dist/resources/extensions/gsd/notification-widget.js +1 -1
- package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
- package/dist/resources/extensions/gsd/state.js +234 -332
- package/dist/resources/extensions/gsd/workflow-events.js +25 -13
- package/dist/resources/skills/create-skill/SKILL.md +2 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +21 -11
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
- package/packages/mcp-server/src/workflow-tools.ts +31 -11
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
- package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
- package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +202 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +19 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +50 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +90 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +57 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +249 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +58 -2
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +96 -2
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +65 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +66 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -0
- package/packages/pi-tui/dist/components/markdown.d.ts +3 -0
- package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/markdown.js +17 -1
- package/packages/pi-tui/dist/components/markdown.js.map +1 -1
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +75 -0
- package/packages/pi-tui/src/components/markdown.ts +22 -1
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
- package/src/resources/extensions/gsd/commands/context.ts +16 -5
- package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +19 -14
- package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
- package/src/resources/extensions/gsd/error-classifier.ts +1 -1
- package/src/resources/extensions/gsd/file-lock.ts +59 -0
- package/src/resources/extensions/gsd/notification-store.ts +19 -1
- package/src/resources/extensions/gsd/notification-widget.ts +1 -1
- package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
- package/src/resources/extensions/gsd/state.ts +274 -344
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/file-lock.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
- package/src/resources/extensions/gsd/workflow-events.ts +34 -25
- package/src/resources/skills/create-skill/SKILL.md +2 -0
- /package/dist/web/standalone/.next/static/{20e8bFnNjxQJflHNodEve → dYVdRaunb2ZSEA8fjkT-V}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{20e8bFnNjxQJflHNodEve → dYVdRaunb2ZSEA8fjkT-V}/_ssgManifest.js +0 -0
|
@@ -42,13 +42,27 @@ function createHost() {
|
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
const pinnedMessageContainer = {
|
|
46
|
+
children: [] as any[],
|
|
47
|
+
addChild(component: any) {
|
|
48
|
+
this.children.push(component);
|
|
49
|
+
},
|
|
50
|
+
removeChild(component: any) {
|
|
51
|
+
const idx = this.children.indexOf(component);
|
|
52
|
+
if (idx !== -1) this.children.splice(idx, 1);
|
|
53
|
+
},
|
|
54
|
+
clear() {
|
|
55
|
+
this.children = [];
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
45
59
|
const host: any = {
|
|
46
60
|
isInitialized: true,
|
|
47
61
|
init: async () => {},
|
|
48
62
|
defaultEditor: { onEscape: undefined },
|
|
49
63
|
editor: {},
|
|
50
64
|
session: { retryAttempt: 0, abortCompaction: () => {}, abortRetry: () => {} },
|
|
51
|
-
ui: { requestRender: () => {} },
|
|
65
|
+
ui: { requestRender: () => {}, terminal: { rows: 50 } },
|
|
52
66
|
footer: { invalidate: () => {} },
|
|
53
67
|
keybindings: {},
|
|
54
68
|
statusContainer: { clear: () => {}, addChild: () => {} },
|
|
@@ -62,6 +76,7 @@ function createHost() {
|
|
|
62
76
|
compactionQueuedMessages: [],
|
|
63
77
|
editorContainer: {},
|
|
64
78
|
pendingMessagesContainer: { clear: () => {} },
|
|
79
|
+
pinnedMessageContainer,
|
|
65
80
|
addMessageToChat: () => {},
|
|
66
81
|
getMarkdownThemeWithSettings: () => ({}),
|
|
67
82
|
formatWebSearchResult: () => "",
|
|
@@ -218,3 +233,236 @@ test("chat-controller keeps serverToolUse output ahead of assistant text when ex
|
|
|
218
233
|
assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
|
|
219
234
|
assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
|
|
220
235
|
});
|
|
236
|
+
|
|
237
|
+
test("chat-controller pins latest assistant text above editor when tool calls are present", async () => {
|
|
238
|
+
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
239
|
+
fg: (_key: string, text: string) => text,
|
|
240
|
+
bg: (_key: string, text: string) => text,
|
|
241
|
+
bold: (text: string) => text,
|
|
242
|
+
italic: (text: string) => text,
|
|
243
|
+
truncate: (text: string) => text,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const host = createHost();
|
|
247
|
+
const toolId = "tool-pin-1";
|
|
248
|
+
const toolCall = {
|
|
249
|
+
type: "toolCall",
|
|
250
|
+
id: toolId,
|
|
251
|
+
name: "exec_command",
|
|
252
|
+
arguments: { cmd: "echo hi" },
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
256
|
+
|
|
257
|
+
assert.equal(host.pinnedMessageContainer.children.length, 0, "pinned zone should be empty at message_start");
|
|
258
|
+
|
|
259
|
+
// Send a message with text followed by a tool call
|
|
260
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
261
|
+
await handleAgentEvent(
|
|
262
|
+
host,
|
|
263
|
+
{
|
|
264
|
+
type: "message_update",
|
|
265
|
+
message: makeAssistant([
|
|
266
|
+
{ type: "text", text: "Looking at the files now." },
|
|
267
|
+
toolCall,
|
|
268
|
+
]),
|
|
269
|
+
assistantMessageEvent: {
|
|
270
|
+
type: "toolcall_end",
|
|
271
|
+
contentIndex: 1,
|
|
272
|
+
toolCall: {
|
|
273
|
+
...toolCall,
|
|
274
|
+
externalResult: {
|
|
275
|
+
content: [{ type: "text", text: "file contents" }],
|
|
276
|
+
details: {},
|
|
277
|
+
isError: false,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
partial: makeAssistant([{ type: "text", text: "Looking at the files now." }, toolCall]),
|
|
281
|
+
},
|
|
282
|
+
} as any,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Pinned zone should now have a DynamicBorder and a Markdown component
|
|
286
|
+
assert.equal(host.pinnedMessageContainer.children.length, 2, "pinned zone should have border + markdown");
|
|
287
|
+
assert.equal(host.pinnedMessageContainer.children[0]?.constructor?.name, "DynamicBorder");
|
|
288
|
+
assert.equal(host.pinnedMessageContainer.children[1]?.constructor?.name, "Markdown");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("chat-controller clears pinned zone when a new assistant message starts", async () => {
|
|
292
|
+
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
293
|
+
fg: (_key: string, text: string) => text,
|
|
294
|
+
bg: (_key: string, text: string) => text,
|
|
295
|
+
bold: (text: string) => text,
|
|
296
|
+
italic: (text: string) => text,
|
|
297
|
+
truncate: (text: string) => text,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const host = createHost();
|
|
301
|
+
const toolCall = {
|
|
302
|
+
type: "toolCall",
|
|
303
|
+
id: "tool-clear-1",
|
|
304
|
+
name: "exec_command",
|
|
305
|
+
arguments: { cmd: "echo hi" },
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
309
|
+
|
|
310
|
+
// Populate the pinned zone
|
|
311
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
312
|
+
await handleAgentEvent(
|
|
313
|
+
host,
|
|
314
|
+
{
|
|
315
|
+
type: "message_update",
|
|
316
|
+
message: makeAssistant([{ type: "text", text: "Working on it." }, toolCall]),
|
|
317
|
+
assistantMessageEvent: {
|
|
318
|
+
type: "toolcall_end",
|
|
319
|
+
contentIndex: 1,
|
|
320
|
+
toolCall: {
|
|
321
|
+
...toolCall,
|
|
322
|
+
externalResult: {
|
|
323
|
+
content: [{ type: "text", text: "ok" }],
|
|
324
|
+
details: {},
|
|
325
|
+
isError: false,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
partial: makeAssistant([{ type: "text", text: "Working on it." }, toolCall]),
|
|
329
|
+
},
|
|
330
|
+
} as any,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
assert.ok(host.pinnedMessageContainer.children.length > 0, "pinned zone should be populated");
|
|
334
|
+
|
|
335
|
+
// Start a new assistant message — pinned zone should clear
|
|
336
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
337
|
+
|
|
338
|
+
assert.equal(host.pinnedMessageContainer.children.length, 0, "pinned zone should clear on new assistant message");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("chat-controller clears pinned zone when the agent turn ends", async () => {
|
|
342
|
+
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
343
|
+
fg: (_key: string, text: string) => text,
|
|
344
|
+
bg: (_key: string, text: string) => text,
|
|
345
|
+
bold: (text: string) => text,
|
|
346
|
+
italic: (text: string) => text,
|
|
347
|
+
truncate: (text: string) => text,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const host = createHost();
|
|
351
|
+
const toolCall = {
|
|
352
|
+
type: "toolCall",
|
|
353
|
+
id: "tool-clear-on-end-1",
|
|
354
|
+
name: "exec_command",
|
|
355
|
+
arguments: { cmd: "echo hi" },
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
359
|
+
|
|
360
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
361
|
+
await handleAgentEvent(
|
|
362
|
+
host,
|
|
363
|
+
{
|
|
364
|
+
type: "message_update",
|
|
365
|
+
message: makeAssistant([{ type: "text", text: "Working on it." }, toolCall]),
|
|
366
|
+
assistantMessageEvent: {
|
|
367
|
+
type: "toolcall_end",
|
|
368
|
+
contentIndex: 1,
|
|
369
|
+
toolCall: {
|
|
370
|
+
...toolCall,
|
|
371
|
+
externalResult: {
|
|
372
|
+
content: [{ type: "text", text: "ok" }],
|
|
373
|
+
details: {},
|
|
374
|
+
isError: false,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
partial: makeAssistant([{ type: "text", text: "Working on it." }, toolCall]),
|
|
378
|
+
},
|
|
379
|
+
} as any,
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
assert.ok(host.pinnedMessageContainer.children.length > 0, "pinned zone should be populated before agent_end");
|
|
383
|
+
|
|
384
|
+
await handleAgentEvent(host, { type: "agent_end" } as any);
|
|
385
|
+
|
|
386
|
+
assert.equal(host.pinnedMessageContainer.children.length, 0, "pinned zone should clear on agent_end");
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("chat-controller clears pinned zone when assistant message ends", async () => {
|
|
390
|
+
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
391
|
+
fg: (_key: string, text: string) => text,
|
|
392
|
+
bg: (_key: string, text: string) => text,
|
|
393
|
+
bold: (text: string) => text,
|
|
394
|
+
italic: (text: string) => text,
|
|
395
|
+
truncate: (text: string) => text,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const host = createHost();
|
|
399
|
+
const toolCall = {
|
|
400
|
+
type: "toolCall",
|
|
401
|
+
id: "tool-msg-end-1",
|
|
402
|
+
name: "exec_command",
|
|
403
|
+
arguments: { cmd: "echo hi" },
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
407
|
+
|
|
408
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
409
|
+
const msgContent = [{ type: "text", text: "Summary after tools." }, toolCall];
|
|
410
|
+
await handleAgentEvent(
|
|
411
|
+
host,
|
|
412
|
+
{
|
|
413
|
+
type: "message_update",
|
|
414
|
+
message: makeAssistant(msgContent),
|
|
415
|
+
assistantMessageEvent: {
|
|
416
|
+
type: "toolcall_end",
|
|
417
|
+
contentIndex: 1,
|
|
418
|
+
toolCall: {
|
|
419
|
+
...toolCall,
|
|
420
|
+
externalResult: {
|
|
421
|
+
content: [{ type: "text", text: "ok" }],
|
|
422
|
+
details: {},
|
|
423
|
+
isError: false,
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
partial: makeAssistant(msgContent),
|
|
427
|
+
},
|
|
428
|
+
} as any,
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
assert.ok(host.pinnedMessageContainer.children.length > 0, "pinned zone should be populated during streaming");
|
|
432
|
+
|
|
433
|
+
// End the assistant message (e.g. before form elicitation) — pinned zone should clear
|
|
434
|
+
await handleAgentEvent(host, { type: "message_end", message: makeAssistant(msgContent) } as any);
|
|
435
|
+
|
|
436
|
+
assert.equal(host.pinnedMessageContainer.children.length, 0, "pinned zone should clear on message_end to prevent duplicate display");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("chat-controller does not pin when there are no tool calls", async () => {
|
|
440
|
+
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
441
|
+
fg: (_key: string, text: string) => text,
|
|
442
|
+
bg: (_key: string, text: string) => text,
|
|
443
|
+
bold: (text: string) => text,
|
|
444
|
+
italic: (text: string) => text,
|
|
445
|
+
truncate: (text: string) => text,
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const host = createHost();
|
|
449
|
+
|
|
450
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
451
|
+
|
|
452
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
453
|
+
await handleAgentEvent(
|
|
454
|
+
host,
|
|
455
|
+
{
|
|
456
|
+
type: "message_update",
|
|
457
|
+
message: makeAssistant([{ type: "text", text: "Just some text, no tools." }]),
|
|
458
|
+
assistantMessageEvent: {
|
|
459
|
+
type: "text_delta",
|
|
460
|
+
contentIndex: 0,
|
|
461
|
+
delta: "Just some text, no tools.",
|
|
462
|
+
partial: makeAssistant([{ type: "text", text: "Just some text, no tools." }]),
|
|
463
|
+
},
|
|
464
|
+
} as any,
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
assert.equal(host.pinnedMessageContainer.children.length, 0, "pinned zone should stay empty without tool calls");
|
|
468
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { Component } from "@gsd/pi-tui";
|
|
1
|
+
import type { Component, TUI } from "@gsd/pi-tui";
|
|
2
|
+
import { visibleWidth } from "@gsd/pi-tui";
|
|
2
3
|
import { theme } from "../theme/theme.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Dynamic border component that adjusts to viewport width.
|
|
7
|
+
* Supports an optional animated spinner in the label area.
|
|
6
8
|
*
|
|
7
9
|
* Note: When used from extensions loaded via jiti, the global `theme` may be undefined
|
|
8
10
|
* because jiti creates a separate module cache. Always pass an explicit color
|
|
@@ -10,11 +12,51 @@ import { theme } from "../theme/theme.js";
|
|
|
10
12
|
*/
|
|
11
13
|
export class DynamicBorder implements Component {
|
|
12
14
|
private color: (str: string) => string;
|
|
15
|
+
private label?: string;
|
|
16
|
+
private spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
17
|
+
private spinnerIndex = 0;
|
|
18
|
+
private spinnerInterval: NodeJS.Timeout | null = null;
|
|
19
|
+
private spinnerColorFn?: (str: string) => string;
|
|
13
20
|
|
|
14
21
|
constructor(color: (str: string) => string = (str) => {
|
|
15
22
|
try { return theme.fg("border", str); } catch { return str; }
|
|
16
|
-
}) {
|
|
23
|
+
}, label?: string) {
|
|
17
24
|
this.color = color;
|
|
25
|
+
this.label = label;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setLabel(label: string | undefined): void {
|
|
29
|
+
this.label = label;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start an animated spinner that prepends to the label.
|
|
34
|
+
* The spinner rotates every 80ms and triggers a re-render via the TUI.
|
|
35
|
+
*/
|
|
36
|
+
startSpinner(ui: TUI, colorFn: (str: string) => string): void {
|
|
37
|
+
this.stopSpinner();
|
|
38
|
+
this.spinnerColorFn = colorFn;
|
|
39
|
+
this.spinnerIndex = 0;
|
|
40
|
+
this.spinnerInterval = setInterval(() => {
|
|
41
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
42
|
+
ui.requestRender();
|
|
43
|
+
}, 80);
|
|
44
|
+
ui.requestRender();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stop the spinner animation. The border reverts to a static label.
|
|
49
|
+
*/
|
|
50
|
+
stopSpinner(): void {
|
|
51
|
+
if (this.spinnerInterval) {
|
|
52
|
+
clearInterval(this.spinnerInterval);
|
|
53
|
+
this.spinnerInterval = null;
|
|
54
|
+
}
|
|
55
|
+
this.spinnerColorFn = undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get isSpinning(): boolean {
|
|
59
|
+
return this.spinnerInterval !== null;
|
|
18
60
|
}
|
|
19
61
|
|
|
20
62
|
invalidate(): void {
|
|
@@ -22,6 +64,20 @@ export class DynamicBorder implements Component {
|
|
|
22
64
|
}
|
|
23
65
|
|
|
24
66
|
render(width: number): string[] {
|
|
67
|
+
const spinnerPrefix = this.spinnerInterval && this.spinnerColorFn
|
|
68
|
+
? this.spinnerColorFn(this.spinnerFrames[this.spinnerIndex]) + " "
|
|
69
|
+
: "";
|
|
70
|
+
|
|
71
|
+
if (this.label) {
|
|
72
|
+
const labelText = ` ${spinnerPrefix}${this.label} `;
|
|
73
|
+
const labelVisible = visibleWidth(labelText);
|
|
74
|
+
const leading = "── ";
|
|
75
|
+
const remaining = Math.max(0, width - labelVisible - leading.length);
|
|
76
|
+
const trailing = "─".repeat(Math.max(1, remaining));
|
|
77
|
+
// Color leading and trailing separately so embedded ANSI in the
|
|
78
|
+
// spinner/label doesn't bleed into the trailing dashes.
|
|
79
|
+
return [this.color(leading) + labelText + this.color(trailing)];
|
|
80
|
+
}
|
|
25
81
|
return [this.color("─".repeat(Math.max(1, width)))];
|
|
26
82
|
}
|
|
27
83
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Loader, Spacer, Text } from "@gsd/pi-tui";
|
|
1
|
+
import { Loader, Markdown, Spacer, Text } from "@gsd/pi-tui";
|
|
2
2
|
|
|
3
3
|
import type { InteractiveModeEvent, InteractiveModeStateHost } from "../interactive-mode-state.js";
|
|
4
4
|
import { theme } from "../theme/theme.js";
|
|
5
5
|
import { AssistantMessageComponent } from "../components/assistant-message.js";
|
|
6
6
|
import { ToolExecutionComponent } from "../components/tool-execution.js";
|
|
7
|
+
import { DynamicBorder } from "../components/dynamic-border.js";
|
|
7
8
|
import { appKey } from "../components/keybinding-hints.js";
|
|
8
9
|
|
|
9
10
|
// Tracks the last processed content index to avoid re-scanning all blocks on every message_update
|
|
@@ -21,6 +22,15 @@ function hasAssistantToolBlocks(message: { content: Array<any> }): boolean {
|
|
|
21
22
|
return message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
// Tracks the latest assistant text for the pinned message zone
|
|
26
|
+
let lastPinnedText = "";
|
|
27
|
+
// Whether any tool execution has been added in this assistant turn (triggers pinned display)
|
|
28
|
+
let hasToolsInTurn = false;
|
|
29
|
+
// Reference to the pinned border so we can toggle its label between working/idle
|
|
30
|
+
let pinnedBorder: DynamicBorder | undefined;
|
|
31
|
+
// Reference to the pinned markdown component below the border
|
|
32
|
+
let pinnedTextComponent: Markdown | undefined;
|
|
33
|
+
|
|
24
34
|
export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
25
35
|
init: () => Promise<void>;
|
|
26
36
|
getMarkdownThemeWithSettings: () => any;
|
|
@@ -43,9 +53,15 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
43
53
|
|
|
44
54
|
host.footer.invalidate();
|
|
45
55
|
|
|
46
|
-
// Reset content index tracker when a new assistant message starts
|
|
56
|
+
// Reset content index tracker and pinned state when a new assistant message starts
|
|
47
57
|
if (event.type === "message_start" && event.message.role === "assistant") {
|
|
48
58
|
lastProcessedContentIndex = 0;
|
|
59
|
+
lastPinnedText = "";
|
|
60
|
+
hasToolsInTurn = false;
|
|
61
|
+
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
62
|
+
pinnedBorder = undefined;
|
|
63
|
+
pinnedTextComponent = undefined;
|
|
64
|
+
host.pinnedMessageContainer.clear();
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
switch (event.type) {
|
|
@@ -58,6 +74,12 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
58
74
|
host.streamingMessage = undefined;
|
|
59
75
|
host.pendingTools.clear();
|
|
60
76
|
host.pendingMessagesContainer.clear();
|
|
77
|
+
host.pinnedMessageContainer.clear();
|
|
78
|
+
lastPinnedText = "";
|
|
79
|
+
hasToolsInTurn = false;
|
|
80
|
+
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
81
|
+
pinnedBorder = undefined;
|
|
82
|
+
pinnedTextComponent = undefined;
|
|
61
83
|
host.compactionQueuedMessages = [];
|
|
62
84
|
host.rebuildChatFromMessages();
|
|
63
85
|
host.updatePendingMessagesDisplay();
|
|
@@ -255,6 +277,59 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
255
277
|
if (contentBlocks.length > 0) {
|
|
256
278
|
lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
|
|
257
279
|
}
|
|
280
|
+
|
|
281
|
+
// Pinned message: mirror the latest assistant text above the editor
|
|
282
|
+
// when tool executions push it out of the viewport.
|
|
283
|
+
const hasTools = contentBlocks.some(
|
|
284
|
+
(c: any) => c.type === "toolCall" || c.type === "serverToolUse",
|
|
285
|
+
);
|
|
286
|
+
if (hasTools) hasToolsInTurn = true;
|
|
287
|
+
|
|
288
|
+
if (hasToolsInTurn) {
|
|
289
|
+
// Collect the latest text block(s) from the assistant message
|
|
290
|
+
let latestText = "";
|
|
291
|
+
for (let i = contentBlocks.length - 1; i >= 0; i--) {
|
|
292
|
+
const c = contentBlocks[i] as any;
|
|
293
|
+
if (c.type === "text" && c.text?.trim()) {
|
|
294
|
+
latestText = c.text.trim();
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (latestText && latestText !== lastPinnedText) {
|
|
300
|
+
lastPinnedText = latestText;
|
|
301
|
+
|
|
302
|
+
if (!pinnedBorder) {
|
|
303
|
+
// First time: create border + text component
|
|
304
|
+
host.pinnedMessageContainer.clear();
|
|
305
|
+
pinnedBorder = new DynamicBorder(
|
|
306
|
+
(str: string) => theme.fg("dim", str),
|
|
307
|
+
"Working · Latest Output",
|
|
308
|
+
);
|
|
309
|
+
pinnedBorder.startSpinner(host.ui, (str: string) => theme.fg("accent", str));
|
|
310
|
+
host.pinnedMessageContainer.addChild(pinnedBorder);
|
|
311
|
+
pinnedTextComponent = new Markdown(latestText, 1, 0, host.getMarkdownThemeWithSettings());
|
|
312
|
+
// Cap pinned content to ~40% of terminal height so tall output
|
|
313
|
+
// doesn't exceed the viewport and cause render flashing.
|
|
314
|
+
pinnedTextComponent.maxLines = Math.max(3, Math.floor(host.ui.terminal.rows * 0.4));
|
|
315
|
+
host.pinnedMessageContainer.addChild(pinnedTextComponent);
|
|
316
|
+
// Hide the separate status loader — the pinned zone replaces it
|
|
317
|
+
if (host.loadingAnimation) {
|
|
318
|
+
host.loadingAnimation.stop();
|
|
319
|
+
host.loadingAnimation = undefined;
|
|
320
|
+
}
|
|
321
|
+
host.statusContainer.clear();
|
|
322
|
+
} else {
|
|
323
|
+
// Update existing markdown component in-place
|
|
324
|
+
pinnedTextComponent?.setText(latestText);
|
|
325
|
+
// Refresh maxLines in case terminal was resized
|
|
326
|
+
if (pinnedTextComponent) {
|
|
327
|
+
pinnedTextComponent.maxLines = Math.max(3, Math.floor(host.ui.terminal.rows * 0.4));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
258
333
|
host.ui.requestRender();
|
|
259
334
|
}
|
|
260
335
|
break;
|
|
@@ -305,6 +380,15 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
305
380
|
}
|
|
306
381
|
host.streamingComponent = undefined;
|
|
307
382
|
host.streamingMessage = undefined;
|
|
383
|
+
// Clear pinned output once the message is finalized in the chat
|
|
384
|
+
// container — prevents duplicate display when the agent continues
|
|
385
|
+
// (e.g. form elicitation) after the assistant message ends.
|
|
386
|
+
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
387
|
+
host.pinnedMessageContainer.clear();
|
|
388
|
+
lastPinnedText = "";
|
|
389
|
+
hasToolsInTurn = false;
|
|
390
|
+
pinnedBorder = undefined;
|
|
391
|
+
pinnedTextComponent = undefined;
|
|
308
392
|
host.footer.invalidate();
|
|
309
393
|
}
|
|
310
394
|
host.ui.requestRender();
|
|
@@ -357,6 +441,16 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
357
441
|
host.streamingMessage = undefined;
|
|
358
442
|
}
|
|
359
443
|
host.pendingTools.clear();
|
|
444
|
+
// Pinned output is only useful while work is actively streaming.
|
|
445
|
+
// Keep chat history as the single source after completion.
|
|
446
|
+
if (pinnedBorder) {
|
|
447
|
+
pinnedBorder.stopSpinner();
|
|
448
|
+
}
|
|
449
|
+
host.pinnedMessageContainer.clear();
|
|
450
|
+
lastPinnedText = "";
|
|
451
|
+
hasToolsInTurn = false;
|
|
452
|
+
pinnedBorder = undefined;
|
|
453
|
+
pinnedTextComponent = undefined;
|
|
360
454
|
await host.checkShutdownRequested();
|
|
361
455
|
host.ui.requestRender();
|
|
362
456
|
break;
|
|
@@ -168,6 +168,7 @@ export class InteractiveMode {
|
|
|
168
168
|
private chatContainer: Container;
|
|
169
169
|
private pendingMessagesContainer: Container;
|
|
170
170
|
private statusContainer: Container;
|
|
171
|
+
private pinnedMessageContainer: Container;
|
|
171
172
|
private defaultEditor: CustomEditor;
|
|
172
173
|
private editor: EditorComponent;
|
|
173
174
|
private autocompleteProvider: CombinedAutocompleteProvider | undefined;
|
|
@@ -285,6 +286,7 @@ export class InteractiveMode {
|
|
|
285
286
|
this.chatContainer = new Container();
|
|
286
287
|
this.pendingMessagesContainer = new Container();
|
|
287
288
|
this.statusContainer = new Container();
|
|
289
|
+
this.pinnedMessageContainer = new Container();
|
|
288
290
|
this.widgetContainerAbove = new Container();
|
|
289
291
|
this.widgetContainerBelow = new Container();
|
|
290
292
|
this.keybindings = KeybindingsManager.create();
|
|
@@ -490,6 +492,7 @@ export class InteractiveMode {
|
|
|
490
492
|
this.ui.addChild(this.chatContainer);
|
|
491
493
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
492
494
|
this.ui.addChild(this.statusContainer);
|
|
495
|
+
this.ui.addChild(this.pinnedMessageContainer);
|
|
493
496
|
this.renderWidgets(); // Initialize with default spacer
|
|
494
497
|
this.ui.addChild(this.widgetContainerAbove);
|
|
495
498
|
this.ui.addChild(this.editorContainer);
|
|
@@ -1396,7 +1399,19 @@ export class InteractiveMode {
|
|
|
1396
1399
|
*/
|
|
1397
1400
|
private renderWidgets(): void {
|
|
1398
1401
|
if (!this.widgetContainerAbove || !this.widgetContainerBelow) return;
|
|
1399
|
-
|
|
1402
|
+
|
|
1403
|
+
// widgetContainerAbove: spacer collapses when pinned content is visible
|
|
1404
|
+
// so there's no extra blank line between pinned output and the editor border.
|
|
1405
|
+
this.widgetContainerAbove.clear();
|
|
1406
|
+
const pinned = this.pinnedMessageContainer;
|
|
1407
|
+
this.widgetContainerAbove.addChild({
|
|
1408
|
+
render: () => pinned.children.length > 0 ? [] : [""],
|
|
1409
|
+
invalidate: () => {},
|
|
1410
|
+
});
|
|
1411
|
+
for (const component of this.extensionWidgetsAbove.values()) {
|
|
1412
|
+
this.widgetContainerAbove.addChild(component);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1400
1415
|
this.renderWidgetContainer(this.widgetContainerBelow, this.extensionWidgetsBelow, false, false);
|
|
1401
1416
|
this.ui.requestRender();
|
|
1402
1417
|
}
|
|
@@ -2264,6 +2279,7 @@ export class InteractiveMode {
|
|
|
2264
2279
|
updateFooter: true,
|
|
2265
2280
|
populateHistory: true,
|
|
2266
2281
|
});
|
|
2282
|
+
this.populatePinnedFromMessages(context.messages);
|
|
2267
2283
|
|
|
2268
2284
|
// Show compaction info if session was compacted
|
|
2269
2285
|
const allEntries = this.sessionManager.getEntries();
|
|
@@ -2287,6 +2303,54 @@ export class InteractiveMode {
|
|
|
2287
2303
|
this.chatContainer.clear();
|
|
2288
2304
|
const context = this.sessionManager.buildSessionContext();
|
|
2289
2305
|
this.renderSessionContext(context);
|
|
2306
|
+
this.populatePinnedFromMessages(context.messages);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/**
|
|
2310
|
+
* After rebuilding chat from messages, pin the last assistant text above the
|
|
2311
|
+
* editor if tool results would otherwise push it out of the viewport.
|
|
2312
|
+
*/
|
|
2313
|
+
private populatePinnedFromMessages(messages: AgentMessage[]): void {
|
|
2314
|
+
this.pinnedMessageContainer.clear();
|
|
2315
|
+
|
|
2316
|
+
// Walk backwards to find the last assistant message
|
|
2317
|
+
let lastAssistant: AssistantMessage | undefined;
|
|
2318
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2319
|
+
const msg = messages[i];
|
|
2320
|
+
if (msg && "role" in msg && msg.role === "assistant") {
|
|
2321
|
+
lastAssistant = msg as AssistantMessage;
|
|
2322
|
+
break;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (!lastAssistant) return;
|
|
2326
|
+
|
|
2327
|
+
// Check if any tool calls follow the last text block
|
|
2328
|
+
const content = lastAssistant.content;
|
|
2329
|
+
let lastTextIndex = -1;
|
|
2330
|
+
let hasToolAfterText = false;
|
|
2331
|
+
for (let i = 0; i < content.length; i++) {
|
|
2332
|
+
if (content[i].type === "text") lastTextIndex = i;
|
|
2333
|
+
}
|
|
2334
|
+
if (lastTextIndex >= 0) {
|
|
2335
|
+
for (let i = lastTextIndex + 1; i < content.length; i++) {
|
|
2336
|
+
if (content[i].type === "toolCall" || content[i].type === "serverToolUse") {
|
|
2337
|
+
hasToolAfterText = true;
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
if (!hasToolAfterText || lastTextIndex < 0) return;
|
|
2343
|
+
|
|
2344
|
+
const textBlock = content[lastTextIndex] as { type: "text"; text: string };
|
|
2345
|
+
const text = textBlock.text?.trim();
|
|
2346
|
+
if (!text) return;
|
|
2347
|
+
|
|
2348
|
+
this.pinnedMessageContainer.addChild(
|
|
2349
|
+
new DynamicBorder((str: string) => theme.fg("dim", str), "Latest Output"),
|
|
2350
|
+
);
|
|
2351
|
+
this.pinnedMessageContainer.addChild(
|
|
2352
|
+
new Markdown(text, 1, 0, this.getMarkdownThemeWithSettings()),
|
|
2353
|
+
);
|
|
2290
2354
|
}
|
|
2291
2355
|
|
|
2292
2356
|
// =========================================================================
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-maxlines.test.d.ts","sourceRoot":"","sources":["../../../src/components/__tests__/markdown-maxlines.test.ts"],"names":[],"mappings":""}
|