From 829893380dd1a252fc73fdedd89cf6c4d855d285 Mon Sep 17 00:00:00 2001 From: minorcell Date: Sun, 8 Feb 2026 15:47:00 +0800 Subject: [PATCH 1/2] fix(core): persist system prompt in session_start history --- docs/user/sessions-history.md | 2 ++ packages/core/src/runtime/session.ts | 4 ++++ packages/core/src/runtime/session_hooks.test.ts | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/docs/user/sessions-history.md b/docs/user/sessions-history.md index 63f79ab0..49124eb6 100644 --- a/docs/user/sessions-history.md +++ b/docs/user/sessions-history.md @@ -29,6 +29,8 @@ Memo 会把会话过程写入本地 JSONL,便于恢复上下文和排查问题 排障时通常重点看:`action`、`observation`、`final`。 +补充:`session_start` 事件会写入本次会话实际使用的系统提示词(`role=system`,`content` 字段)。 + ## 三、如何恢复历史 在输入框输入: diff --git a/packages/core/src/runtime/session.ts b/packages/core/src/runtime/session.ts index 4471de4e..4388ec20 100644 --- a/packages/core/src/runtime/session.ts +++ b/packages/core/src/runtime/session.ts @@ -276,7 +276,11 @@ class AgentSessionImpl implements AgentSession { const effectiveMaxPromptTokens = this.options.maxPromptTokens ?? DEFAULT_MAX_PROMPT_TOKENS if (!this.sessionStartEmitted) { + const systemPrompt = + this.history[0]?.role === 'system' ? this.history[0].content : undefined await this.emitEvent('session_start', { + content: systemPrompt, + role: systemPrompt ? 'system' : undefined, meta: { mode: this.mode, cwd: process.cwd(), diff --git a/packages/core/src/runtime/session_hooks.test.ts b/packages/core/src/runtime/session_hooks.test.ts index 9fa0cea2..088a1d95 100644 --- a/packages/core/src/runtime/session_hooks.test.ts +++ b/packages/core/src/runtime/session_hooks.test.ts @@ -286,6 +286,11 @@ describe('session hooks & middleware', () => { const sessionStart = events.find((event) => event.type === 'session_start') assert.ok(sessionStart, 'session_start should exist') assert.strictEqual(sessionStart?.meta?.cwd, process.cwd()) + assert.strictEqual(sessionStart?.role, 'system') + assert.ok( + typeof sessionStart?.content === 'string' && sessionStart.content.length > 0, + 'session_start should include system prompt content', + ) const actionEvent = events.find((event) => event.type === 'action') assert.ok(actionEvent, 'action event should exist') From dc88c99b892cc6385fb5a0dab529eb3338e5c632 Mon Sep 17 00:00:00 2001 From: minorcell Date: Sun, 8 Feb 2026 15:53:44 +0800 Subject: [PATCH 2/2] refactor: remove dollar-prefixed shell execution in tui --- docs/user/cli-tui.md | 11 ------- packages/cli/src/tui/App.tsx | 42 ------------------------- packages/cli/src/tui/commands.test.ts | 29 +++++++++++++++++ packages/cli/src/tui/commands.ts | 12 ------- packages/cli/src/tui/slash/help_text.ts | 1 - 5 files changed, 29 insertions(+), 66 deletions(-) create mode 100644 packages/cli/src/tui/commands.test.ts diff --git a/docs/user/cli-tui.md b/docs/user/cli-tui.md index b2ae52f0..57e3e4ec 100644 --- a/docs/user/cli-tui.md +++ b/docs/user/cli-tui.md @@ -54,17 +54,6 @@ echo "your prompt" | memo 此外,直接输入 `exit`(不加 `/`)也可退出。 -### 本地 Shell 命令 - -输入以 `$` 开头的命令会在本地 `cwd` 执行: - -```text -$ git status -$ rg "createAgentSession" -n -``` - -这是用户主动执行,不走模型工具审批流程。 - ## 快捷键 - `Enter`:发送 diff --git a/packages/cli/src/tui/App.tsx b/packages/cli/src/tui/App.tsx index abf02000..cd8f65e6 100644 --- a/packages/cli/src/tui/App.tsx +++ b/packages/cli/src/tui/App.tsx @@ -2,8 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { readFile, stat } from 'node:fs/promises' import { basename } from 'node:path' import { randomUUID } from 'node:crypto' -import { exec } from 'node:child_process' -import { promisify } from 'node:util' import { Box, useApp, Text } from 'ink' import { createAgentSession, @@ -29,8 +27,6 @@ import { resolveSlashCommand } from './commands' import { checkForUpdate, findLocalPackageInfoSync } from './version' import { SetupWizard } from './components/setup/SetupWizard' -const execAsync = promisify(exec) - export type AppProps = { sessionOptions: AgentSessionOptions providerName: string @@ -453,32 +449,6 @@ export function App({ [appendSystemMessage, busy, currentModel, currentProvider, persistCurrentProvider], ) - const runShellCommand = useCallback( - async (command: string) => { - if (!command.trim()) { - appendSystemMessage('Shell Command', 'Usage: $ (e.g. $ git status)') - return - } - setBusy(true) - try { - const { stdout, stderr } = await execAsync(command, { - cwd, - maxBuffer: 5 * 1024 * 1024, // prevent truncation on large outputs - }) - const output = [stdout?.trim(), stderr?.trim()].filter(Boolean).join('\n') - appendSystemMessage('Shell Result', output || '(no output)') - } catch (err) { - const error = err as Error & { stdout?: string; stderr?: string } - const parts = [error.stdout?.trim(), error.stderr?.trim(), error.message] - const message = parts.filter(Boolean).join('\n') - appendSystemMessage('Shell Error', message || 'Command failed') - } finally { - setBusy(false) - } - }, - [appendSystemMessage, cwd], - ) - const handleCommand = useCallback( async (raw: string) => { const result = resolveSlashCommand(raw, { @@ -542,10 +512,6 @@ Make the AGENTS.md concise but informative, following best practices for AI agen } return } - if (result.kind === 'shell_command') { - await runShellCommand(result.command) - return - } appendSystemMessage(result.title, result.content) }, [ @@ -560,7 +526,6 @@ Make the AGENTS.md concise but informative, following best practices for AI agen currentProvider, contextLimit, providersState, - runShellCommand, ], ) @@ -601,13 +566,6 @@ Make the AGENTS.md concise but informative, following best practices for AI agen } if (!session || busy) return - const trimmed = value.trim() - if (trimmed.startsWith('$')) { - const command = trimmed.slice(1).trim() - setInputHistory((prev) => [...prev, value]) - await runShellCommand(command) - return - } if (value.startsWith('/')) { await handleCommand(value) return diff --git a/packages/cli/src/tui/commands.test.ts b/packages/cli/src/tui/commands.test.ts new file mode 100644 index 00000000..f45e188e --- /dev/null +++ b/packages/cli/src/tui/commands.test.ts @@ -0,0 +1,29 @@ +import assert from 'node:assert' +import { describe, test } from 'vitest' +import { resolveSlashCommand } from './commands' + +const context = { + configPath: '/tmp/config.toml', + providerName: 'test-provider', + model: 'test-model', + mcpServers: {}, + providers: [], + contextLimit: 120000, +} + +describe('resolveSlashCommand', () => { + test('treats /$ command as unknown', () => { + const result = resolveSlashCommand('/$ git status', context) + assert.strictEqual(result.kind, 'message') + assert.strictEqual(result.title, 'Unknown') + assert.ok(result.content.includes('Unknown command: /$ git status')) + }) + + test('help does not include shell execution command', () => { + const result = resolveSlashCommand('/help', context) + assert.strictEqual(result.kind, 'message') + assert.strictEqual(result.title, 'Help') + assert.ok(!result.content.includes('Execute shell command')) + assert.ok(!result.content.includes('\n $')) + }) +}) diff --git a/packages/cli/src/tui/commands.ts b/packages/cli/src/tui/commands.ts index 83ee8c2d..f73ec6a8 100644 --- a/packages/cli/src/tui/commands.ts +++ b/packages/cli/src/tui/commands.ts @@ -17,7 +17,6 @@ export type SlashCommandResult = | { kind: 'switch_model'; provider: ProviderConfig } | { kind: 'set_context_limit'; limit: number } | { kind: 'init_agents_md' } - | { kind: 'shell_command'; command: string } export function resolveSlashCommand(raw: string, context: SlashResolveContext): SlashCommandResult { const [commandRaw, ...rest] = raw.trim().slice(1).split(/\s+/) @@ -77,17 +76,6 @@ export function resolveSlashCommand(raw: string, context: SlashResolveContext): } case 'init': return { kind: 'init_agents_md' } - case '$': { - const command = rest.join(' ').trim() - if (!command) { - return { - kind: 'message', - title: 'Shell Command', - content: 'Usage: $ (e.g. $ git status)', - } - } - return { kind: 'shell_command', command } - } case 'models': if (!context.providers.length) { return { diff --git a/packages/cli/src/tui/slash/help_text.ts b/packages/cli/src/tui/slash/help_text.ts index 143d9dc9..6546bfcb 100644 --- a/packages/cli/src/tui/slash/help_text.ts +++ b/packages/cli/src/tui/slash/help_text.ts @@ -5,7 +5,6 @@ const SLASH_LINES = buildSlashHelpLines().join('\n') export const HELP_TEXT = `Available commands: ${SLASH_LINES} exit Exit the session (no slash) - $ Execute shell command (e.g. $ git status) Shortcuts: Enter Send message