Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions src/components/FormTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,30 @@ export function FormTextArea({

return (
<Box flexDirection="column">
<Box
flexDirection="column"
borderStyle="single"
borderLeft={false}
borderRight={false}
borderTop={false}
borderColor={theme.colors.border}
>
<Box flexDirection="column">
<Text color={theme.colors.text}>{name}</Text>
<Text color={theme.colors.muted}>{helpText}</Text>
</Box>
{hidden > 0 && <Text color={theme.colors.muted}>… (+{hidden} earlier lines)</Text>}
{visible.length === 0 ? (
<Text color={theme.colors.muted}>
{placeholder}
<Text inverse> </Text>
</Text>
) : (
visible.map((line, i) => (
<Text key={i}>
{line}
{i === visible.length - 1 ? <Text inverse> </Text> : null}
<Box
flexDirection="column"
borderStyle="round"
borderColor={focused ? theme.colors.focus : theme.colors.border}
>
{hidden > 0 && <Text color={theme.colors.muted}>… (+{hidden} earlier lines)</Text>}
{visible.length === 0 ? (
<Text color={theme.colors.muted}>
{placeholder}
<Text inverse> </Text>
</Text>
))
)}
) : (
visible.map((line, i) => (
<Text key={i}>
{line}
{i === visible.length - 1 ? <Text inverse> </Text> : null}
</Text>
))
)}
</Box>
</Box>
);
}
13 changes: 3 additions & 10 deletions src/components/HarnessWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -867,8 +867,9 @@ function MemoryStep({
helpText="how should the harness remember conversations?"
options={MEMORY_OPTIONS}
focusedIndex={index}
selectedIndex={editing ? index : undefined}
/>
{value.kind === "byo" && (
{editing && value.kind === "byo" && (
<FormTextInput
name="Memory ARN"
helpText="the ARN of an existing AgentCore Memory"
Expand Down Expand Up @@ -1086,15 +1087,7 @@ function PromptStep({
onChange={onChange}
/>
{value !== "" && (
<Box
borderStyle="single"
borderColor={theme.colors.border}
borderLeft={false}
borderRight={false}
borderBottom={false}
>
<Text color={theme.colors.muted}>{`${value.length} chars · ctrl+d continues`}</Text>
</Box>
<Text color={theme.colors.muted}>{`${value.length} chars · ctrl+d continues`}</Text>
)}
</Box>
);
Expand Down
8 changes: 8 additions & 0 deletions src/handlers/harness/create/create.screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ describe("harness create wizard", () => {

// Step: prompt — type a prompt with a newline, then ctrl+d.
await waitForText(r.lastFrame, "type or paste the agent's instructions");
const promptFrame = r.lastFrame()!;
const promptHelpLine = promptFrame
.split("\n")
.findIndex((line) => line.includes("type or paste the agent's instructions"));
expect(promptFrame.split("\n")[promptHelpLine + 1]).toContain("╭");
expect(promptFrame).toContain("╰");
await r.write("You are helpful.");
await r.press("return"); // newline
await r.write("Be brief.");
Expand Down Expand Up @@ -323,7 +329,9 @@ describe("harness create wizard", () => {
await waitForText(r.lastFrame, "how should the harness remember conversations?");
await r.press("down"); // bring your own
await waitForText(r.lastFrame, "● bring your own");
expect(r.lastFrame()).not.toContain("Memory ARN");
await r.press("return"); // focus the memory arn field
await waitForText(r.lastFrame, "Memory ARN");
await r.press("return"); // empty → error
await waitForText(r.lastFrame, "enter the ARN of an existing AgentCore Memory");
await r.write("arn:aws:bedrock-agentcore:us-east-1:123:memory/m-1");
Expand Down
30 changes: 30 additions & 0 deletions src/handlers/harness/update/update.screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,36 @@ describe("harness update wizard", () => {
r.unmount();
});

test("reveals the current bring-your-own memory ARN only after enter", async () => {
const core = coreForUpdate();
const current = currentHarness();
const memoryArn = "arn:aws:bedrock-agentcore:us-east-1:123:memory/existing";
current.harness!.memory = {
agentCoreMemoryConfiguration: { arn: memoryArn },
};
core.harness.setGetResponse(current);
const r = renderScreen("/agentcore/harness/update/MyHarness-abc123", { core });

await waitForText(r.lastFrame, "● keep current");
await r.press("return");

await waitForText(r.lastFrame, "● bring your own");
expect(r.lastFrame()).not.toContain("Memory ARN");
expect(r.lastFrame()).not.toContain(memoryArn);

await r.press("return");
await waitForText(r.lastFrame, memoryArn);

await r.press("escape");
await waitFor(() => !(r.lastFrame() ?? "").includes(memoryArn));
expect(r.lastFrame()).not.toContain("Memory ARN");
expect(r.lastFrame()).toContain("● bring your own");

await r.press("return");
await waitForText(r.lastFrame, memoryArn);
r.unmount();
});

test("changing the model submits a request with just that field", async () => {
const core = coreForUpdate();
const r = renderScreen("/agentcore/harness/update/MyHarness-abc123", { core });
Expand Down
35 changes: 31 additions & 4 deletions src/handlers/project/create/create.screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,16 @@ describe("project create wizard", () => {
await r.press("down"); // harness
await r.press("return");

// Model step: providers and the selected provider's fields share one page.
// Model step: fields stay hidden until the provider is confirmed.
await waitForText(r.lastFrame, "choose a model");
expect(r.lastFrame()).toContain("● bedrock");
expect(r.lastFrame()).not.toContain("bedrock (recommended)");
expect(r.lastFrame()).toContain("○ openai");
expect(r.lastFrame()).toContain("○ gemini");
expect(r.lastFrame()).toContain("○ litellm");
expect(r.lastFrame()).toContain(DEFAULT_MODEL_ID);
expect(r.lastFrame()).not.toContain(DEFAULT_MODEL_ID);
await r.press("return"); // focus model id
await waitForText(r.lastFrame, DEFAULT_MODEL_ID);
await r.press("return"); // accept model id

// Review: the summary names the project, type, model, and directory.
Expand Down Expand Up @@ -247,6 +248,32 @@ describe("project create wizard", () => {
r.unmount();
});

test("reveals model fields only after enter and hides them again on escape", async () => {
const r = renderScreen("/agentcore/project/create");

await waitForText(r.lastFrame, "name your project");
await r.write("ModelApp");
await r.press("return");
await r.press("down"); // harness
await r.press("return");
await waitForText(r.lastFrame, "choose a model");

await r.press("down"); // openai
await waitForText(r.lastFrame, "● openai");
expect(r.lastFrame()).not.toContain("model ID");
expect(r.lastFrame()).not.toContain("API key ARN");

await r.press("return");
await waitForText(r.lastFrame, "model ID");
expect(r.lastFrame()).toContain("API key ARN");

await r.press("escape");
await waitFor(() => !(r.lastFrame() ?? "").includes("model ID"));
expect(r.lastFrame()).not.toContain("API key ARN");
expect(r.lastFrame()).toContain("● openai");
r.unmount();
});

test("the model picker remains readable in an 80x24 terminal", async () => {
const r = renderScreen("/agentcore/project/create");
await r.resize(80, 24);
Expand All @@ -268,8 +295,8 @@ describe("project create wizard", () => {
expect(frame).toContain("○ openai");
expect(frame).toContain("○ gemini");
expect(frame).toContain("○ litellm");
expect(frame).toContain("model ID");
expect(frame).toContain(DEFAULT_MODEL_ID);
expect(frame).not.toContain("model ID");
expect(frame).not.toContain(DEFAULT_MODEL_ID);
expect(frame).toContain("[enter] continue");
expect(frame).toContain("[esc] back");
r.unmount();
Expand Down
44 changes: 23 additions & 21 deletions src/handlers/project/create/screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -672,28 +672,30 @@ function ModelStep({
helpText="the provider and model that will power the harness"
options={options}
focusedIndex={providerIndex}
selectedIndex={focusedField !== null ? providerIndex : undefined}
/>
{fields.map((field, fieldIndex) => (
<FormTextInput
key={`${value.provider}.${field.key}`}
name={field.name}
helpText={field.helpText}
placeholder={field.placeholder}
errorText=""
value={config[field.key]}
onChange={(next) => {
onChange({
...value,
configs: {
...value.configs,
[value.provider]: { ...config, [field.key]: next },
},
});
setError(null);
}}
focused={focusedField === fieldIndex}
/>
))}
{focusedField !== null &&
fields.map((field, fieldIndex) => (
<FormTextInput
key={`${value.provider}.${field.key}`}
name={field.name}
helpText={field.helpText}
placeholder={field.placeholder}
errorText=""
value={config[field.key]}
onChange={(next) => {
onChange({
...value,
configs: {
...value.configs,
[value.provider]: { ...config, [field.key]: next },
},
});
setError(null);
}}
focused={focusedField === fieldIndex}
/>
))}
{error && (
<Text key="error" color={theme.colors.error}>
{error}
Expand Down
Loading