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
2 changes: 1 addition & 1 deletion packages/core/src/agent/agent-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ export class AgentLoop {
if (message.role === "user") {
this.memory.addUser(content);
} else {
this.memory.addSystem(content);
this.memory.addSystem(content, { hidden: message.hidden });
}
}

Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/agent/conversation-memory.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect } from "vitest";
import type { SystemMessage } from "@step-cli/protocol";
import {
ConversationMemory,
type MemoryConfig,
Expand Down Expand Up @@ -100,6 +101,23 @@ describe("ConversationMemory", () => {
expect(state.messages).toHaveLength(1);
expect(state.messages[0]!.role).toBe("system");
});

it("preserves hidden flag through export/load round-trip", () => {
const memory = new ConversationMemory(makeConfig());
memory.addSystem("hidden instruction", { hidden: true });
memory.addSystem("visible instruction");

const exported = memory.exportState();
const memory2 = new ConversationMemory(makeConfig());
memory2.loadState(exported);

const restored = memory2.exportState();
expect(restored.messages).toHaveLength(2);
expect(restored.messages[0]!.role).toBe("system");
expect((restored.messages[0]! as SystemMessage).hidden).toBe(true);
expect(restored.messages[1]!.role).toBe("system");
expect((restored.messages[1]! as SystemMessage).hidden).toBeUndefined();
});
});

describe("recordDecision", () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/agent/conversation-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,12 @@ export class ConversationMemory {
this.invalidateContextAssemblyForTranscriptMutation();
}

addSystem(content: string): void {
this.messages.push({ role: "system", content });
addSystem(content: string, options?: { hidden?: boolean }): void {
this.messages.push({
role: "system",
content,
...(options?.hidden ? { hidden: true } : undefined),
});
this.invalidateContextAssemblyForTranscriptMutation();
const normalized = shortenLine(content, this.config.decisionEntryMaxChars);
if (normalized.length > 0) {
Expand Down Expand Up @@ -2048,6 +2052,7 @@ function cloneChatMessage(message: ChatMessage): ChatMessage {
return {
role: "system",
content: message.content,
...(message.hidden ? { hidden: true } : undefined),
};
}

Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/plugins/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,15 @@ export class PluginManager {
strategy: "head_tail",
});

injected.push({
role: message.role,
content: truncated.text,
});
injected.push(
message.role === "system"
? {
role: "system",
content: truncated.text,
...(message.hidden ? { hidden: true } : undefined),
}
: { role: "user", content: truncated.text },
);
}

if (injected.length >= MAX_TOTAL_INJECTED_MESSAGES) {
Expand Down
19 changes: 15 additions & 4 deletions packages/core/src/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,21 @@ export interface PluginUserMessage {
content: string;
}

export type PluginInjectedMessage = {
role: "system" | "user";
content: string;
};
export type PluginInjectedMessage =
| {
role: "system";
content: string;
/**
* When true, the message is only for the model context and should not
* be rendered in the UI. Only system messages support this flag; the
* agent loop stores injected user messages without it.
*/
hidden?: boolean;
}
| {
role: "user";
content: string;
};

export interface PluginHookContext {
workspaceRoot: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export interface JsonSchema {
export interface SystemMessage {
role: "system";
content: string;
/** Internal messages injected by plugins; should not be rendered in the UI. */
hidden?: boolean;
}

export type UserAttachmentSource =
Expand Down
1 change: 1 addition & 0 deletions skills/builtin/src/plan-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export function createPlanPlugin(manager: PlanManager): ToolPlugin {
{
role: "system",
content: renderPlanInjectedMessage(snapshot),
hidden: true,
},
],
};
Expand Down
1 change: 1 addition & 0 deletions skills/builtin/src/skill-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export function createSkillPlugin(
{
role: "system" as const,
content: renderInjectedSkills(injectedContents),
hidden: true,
},
]
: undefined;
Expand Down
1 change: 1 addition & 0 deletions skills/builtin/src/subagent-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,7 @@ class BackgroundSubtaskManager {
messages.push({
role: "system",
content: MAIN_ORCHESTRATION_REMINDER,
hidden: true,
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/runtime/local-opentui-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class LocalOpenTuiTranscriptBridge implements StepCliTuiTranscriptControl
return [
...this.sessionEntries,
...this.localEntries.map(stripLocalTranscriptEntry),
];
].filter((entry) => !entry.hidden);
}

subscribe(
Expand Down Expand Up @@ -900,6 +900,7 @@ function mapChatMessageToTranscriptEntry(
role: "system",
caption: null,
content: message.content,
hidden: message.hidden,
};
}
}
Expand Down
32 changes: 17 additions & 15 deletions src/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1914,21 +1914,23 @@ function buildTranscriptItems(
): TranscriptItem[] {
return [
buildWelcomeTranscriptItem(width),
...entries.map((entry, index) => {
const identity = resolveTranscriptIdentity(entry);
const body = compactToolTranscriptContent(entry);
const lines = wrapMultiline(body, Math.max(12, width - 4));
return {
id:
entry.id ||
`message:${index}:${identity.badge}:${identity.caption ?? ""}`,
...identity,
backgroundColor: resolveTranscriptBackground(entry, theme),
border: false,
lines,
truncated: false,
};
}),
...entries
.filter((entry) => !entry.hidden)
.map((entry, index) => {
const identity = resolveTranscriptIdentity(entry);
const body = compactToolTranscriptContent(entry);
const lines = wrapMultiline(body, Math.max(12, width - 4));
return {
id:
entry.id ||
`message:${index}:${identity.badge}:${identity.caption ?? ""}`,
...identity,
backgroundColor: resolveTranscriptBackground(entry, theme),
border: false,
lines,
truncated: false,
};
}),
];
}

Expand Down
1 change: 1 addition & 0 deletions src/tui/transcript-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export function buildTranscriptClipboardText(
entries: readonly StepCliTuiTranscriptEntry[],
): string {
return entries
.filter((entry) => !entry.hidden)
.map((entry) => formatTranscriptClipboardBlock(entry))
.filter((block) => block.length > 0)
.join("\n\n")
Expand Down
2 changes: 2 additions & 0 deletions src/tui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export interface StepCliTuiTranscriptEntry {
role: "assistant" | "user" | "tool" | "system";
content: string;
caption: string | null;
/** Internal message that should not be rendered in the transcript. */
hidden?: boolean;
}

export interface StepCliTuiQueuedTurnEntry {
Expand Down
Loading