mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
Merge pull request #3484 from deepseek-harness/fix/fork-resume-system-prompt
fix(web): avoid repeated system prompt on resume
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md
|
||||
2026-08-09-client-conversation-node-assembly.md: 425975944f7b23575c4f716dee8754dc6b3ab54c
|
||||
2026-08-09-client-conversation-node-assembly.zh.md: 96439fa951cdc1d6abe397ee37661291c0f954a7
|
||||
2026-08-09-client-conversation-node-assembly.md: 50e11b04d6461e99cdddb8e5e2d94bf864228841
|
||||
2026-08-09-client-conversation-node-assembly.zh.md: 7be1cd1b7f356caf51b8003821369771b0a6e269
|
||||
|
||||
+2
-2
@@ -278,7 +278,7 @@ Page size, record packing, the number of history loads, and RAF coalescing affec
|
||||
|---|---|---|---|
|
||||
| Inbox | `none` | No Node | Recompute next-step ID state along the Reader chain when prepend supplies earlier splices; next-turn creates no Chat Context |
|
||||
| Message | Immediate by default | `user`, `steering`, or `context` | Window-gap repair can reclassify the same message key |
|
||||
| Request Prompt | Immediate by default | One `system-prompt` for every header carrying a non-empty system field | A step's first header anchors before its request messages; a later same-step series anchors after its surface rewrite; prepend of the preceding header can correct a partial-window anchor |
|
||||
| Request Prompt | Immediate by default | One non-empty `system-prompt` for the initial request, each explicit series, or a real system change | A step's first header anchors before its request messages; prepend can hide a conservatively rendered resume after its preceding header proves the system unchanged |
|
||||
| Assistant | RAF for scalar chunks and packed runs, immediate for final, none for pure usage/finish | Same-key `assistant-step` with running/settled/interrupted status | Scalar and packed reducers are equivalent; Matches support fallback without `step/start`; Location close produces interruption presentation |
|
||||
| Tool | Immediate by default | One recursive `tool-call` root containing all `subCalls` | A result-only history window supports fallback; running→settled retains its key |
|
||||
| Command | Immediate by default | Ordinary `command` or integrated `manual-compaction` | Checkpoint arrival may change the anchor without changing the Context key |
|
||||
@@ -291,7 +291,7 @@ Page size, record packing, the number of history loads, and RAF coalescing affec
|
||||
|
||||
Inbox demonstrates that every Event can be a start-only instantaneous-state Context; not every business requires a start/update pair. Reader links each next-step state to the prior same-kind Context instead of inventing a lifecycle ID for the entire Inbox. The state itself shares immutable pending splice nodes and one current claimed-batch Set, while unconsumed next-turn input remains outside Conversation because no Chat or Trajectory classification reads it.
|
||||
|
||||
Request Prompt demonstrates shared pure interpretation without shared target State: Chat and Trajectory call `inspectRequestPrompt()` from their own Definitions. The function canonicalizes the full header and classifies model-visible system/tool differences; each target then chooses its own output. Chat materializes every header carrying a non-empty system field, including `series` snapshots that repeat an unchanged header for an explicitly declared series or a post-replacement request, while Trajectory retains the complete request fact and its change classification. Ordinary append-only later Turns do not write another unchanged header. The first header in a Step follows the provider envelope rather than the header Event position: step one uses the owning Turn start and later steps use their Step start, placing the system field before the request's user-role messages; a later header in the same Step stays at its own Event after the surface rewrite that began the new series. When the preceding header is outside a partial window, a non-`initial` header stays at its own Event until prepend supplies that predecessor. Every header is a full snapshot, so a first loaded `resume`, `change`, or `series` header can render its system field without fabricating a comparison to unloaded history.
|
||||
Request Prompt demonstrates shared pure interpretation without shared target State: Chat and Trajectory call `inspectRequestPrompt()` from their own Definitions. The function canonicalizes the full header and classifies model-visible system/tool differences; each target then chooses its own output. Chat materializes a non-empty initial system field, a real system change, and each `series` snapshot that explicitly begins a message series or follows a surface replacement. An unchanged `resume` remains in Trajectory and reconstruction state but does not repeat the visible Chat row once its predecessor is loaded. Ordinary append-only later Turns do not write another unchanged header. The first header in a Step follows the provider envelope rather than the header Event position: step one uses the owning Turn start and later steps use their Step start, placing the system field before the request's user-role messages; a later header in the same Step stays at its own Event after the surface rewrite that began the new series. When the preceding header is outside a partial window, a non-`initial` header stays at its own Event and renders conservatively. Prepending an identical predecessor hides an unchanged resume without withdrawing its stable Node key; a real change remains visible. Every header is a full snapshot, so a first loaded `resume`, `change`, or `series` header can render its system field without fabricating a comparison to unloaded history ([resume presentation decision](../bug-fix/2026-09-03-resume-headers-do-not-repeat-system-prompts.md)).
|
||||
|
||||
Retry, Assistant, and Turn Tail demonstrate independent claims on one Event. Each Definition updates only its own State and produces its own atomic Chat Node.
|
||||
|
||||
|
||||
+2
-2
@@ -278,7 +278,7 @@ Chat `order` 的结构性变化仍可能重排当前可见 key;纯 data 更新
|
||||
|---|---|---|---|
|
||||
| Inbox | `none` | 不生成 Node | prepend 补前序 splice 时沿 Reader 链重算 next-step ID state;next-turn 不创建 Chat Context |
|
||||
| Message | 默认 immediate | `user`、`steering` 或 `context` | window gap 修复可让同一 message key 重新分类 |
|
||||
| Request Prompt | 默认 immediate | 每条带非空 system 字段的 header 都生成一个 `system-prompt` | Step 首条 header 锚定在请求消息之前;同 step 后续序列锚定在表层改写之后;prepend 补入前序 header 后可纠正部分窗口的锚点 |
|
||||
| Request Prompt | 默认 immediate | 初始请求、每个显式序列或真实 system 变化各生成一个非空 `system-prompt` | Step 首条 header 锚定在请求消息之前;prepend 补入前序 header 并证明 system 未变后,可隐藏此前保守渲染的 resume |
|
||||
| Assistant | scalar chunk 与 packed run 为 RAF,final immediate,纯 usage/finish 为 none | 同 key `assistant-step`,状态为 running/settled/interrupted | scalar 与 packed reducer 等价;缺 `step/start` 可先用 Matches fallback;Location close 生成中断表现 |
|
||||
| Tool | 默认 immediate | 一个递归 `tool-call` root,包含全部 `subCalls` | result-only 历史窗口可 fallback;running→settled 保持 key |
|
||||
| Command | 默认 immediate | 普通 `command` 或集成 `manual-compaction` | checkpoint 到达可改变 anchor,但不改变 Context key |
|
||||
@@ -291,7 +291,7 @@ Chat `order` 的结构性变化仍可能重排当前可见 key;纯 data 更新
|
||||
|
||||
Inbox 展示了“每条 Event 都是一个 start-only 瞬间态 Context”,不是所有业务都需要 start/update 配对。每个 next-step state 通过 Reader 与前一个同 kind Context 形成连续 fold,而非给整个 Inbox 人工制造生命周期 ID。state 自身共享不可变 pending splice 节点和一个当前 claimed-batch Set;未消费的 next-turn input 不进入 Conversation,因为 Chat 与 Trajectory 都不读取它来分类。
|
||||
|
||||
Request Prompt 展示了如何在不共享 target State 的前提下共用纯解释逻辑:Chat 与 Trajectory 各自在自己的 Definition 中调用 `inspectRequestPrompt()`。该函数规范化完整 header,并判定面向模型的 system/tool 差异;随后每个 target 自行选择产物。Chat 会物化每条带非空 system 字段的 header,包括为显式声明的序列或表层替换后的请求重复未变 header 的 `series` 快照;Trajectory 则保留完整请求事实及其变化分类。普通的仅追加后续 Turn 不会再次写入未变 header。一个 Step 中的首条 header 遵循提供方信封,而不是 header Event 位置:step one 使用所属 Turn start,后续 step 使用各自的 Step start,把 system 字段放到该请求的 user-role 消息之前;同一 Step 的后续 header 保留在开启新序列的表层改写之后。部分窗口未包含前序 header 时,非 `initial` header 会保留在自身 Event,直到 prepend 补入该前序 header。每条 header 都是完整快照,因此已加载窗口中的首条 `resume`、`change` 或 `series` header 无需凭空构造与未加载历史的比较,也能渲染其 system 字段。
|
||||
Request Prompt 展示了如何在不共享 target State 的前提下共用纯解释逻辑:Chat 与 Trajectory 各自在自己的 Definition 中调用 `inspectRequestPrompt()`。该函数规范化完整 header,并判定面向模型的 system/tool 差异;随后每个 target 自行选择产物。Chat 会物化非空的初始 system 字段、真实 system 变化,以及每个显式开启消息序列或紧随表层替换的 `series` 快照。未变化的 `resume` 会留在 Trajectory 和重建状态中,但前序 header 已加载时不会重复可见的 Chat 行。普通的仅追加后续 Turn 不会再次写入未变 header。一个 Step 中的首条 header 遵循提供方信封,而不是 header Event 位置:step one 使用所属 Turn start,后续 step 使用各自的 Step start,把 system 字段放到该请求的 user-role 消息之前;同一 Step 的后续 header 保留在开启新序列的表层改写之后。部分窗口未包含前序 header 时,非 `initial` header 会保留在自身 Event 并保守渲染。prepend 补入相同的前序 header 后,内容未变的 resume 会隐藏但不撤回其稳定 Node key;真实变化仍然可见。每条 header 都是完整快照,因此已加载窗口中的首条 `resume`、`change` 或 `series` header 无需凭空构造与未加载历史的比较,也能渲染其 system 字段([resume 展示决策](../bug-fix/2026-09-03-resume-headers-do-not-repeat-system-prompts.zh.md))。
|
||||
|
||||
Retry、Assistant 和 Turn Tail 展示了同一 Event 被多个 Definition 独立认领。每个 Definition 只更新自己的 State,最终分别生成原子 Chat Node。
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-03-resume-headers-do-not-repeat-system-prompts.md
|
||||
2026-09-03-resume-headers-do-not-repeat-system-prompts.md: de5e509c9e57af35bc36c24004457f02b63fd99d
|
||||
2026-09-03-resume-headers-do-not-repeat-system-prompts.zh.md: 008e25d8d507aac8fbaec7230bad433732f0efd0
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Agent Note: Resume headers do not repeat system prompts
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-03-resume-headers-do-not-repeat-system-prompts.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Forking a Session copies the source history into the child. The child's first model request then records a `request/header` with reason `resume`, even when its system field is identical to the preceding copied header. Chat treated every resume header as a new display point, so continuing the fork showed a second `System prompt` row and suggested that the system prompt had been injected twice. The provider request still carried the system field once; the duplicate existed only in Chat presentation.
|
||||
|
||||
## Decision
|
||||
|
||||
The durable resume header records the request boundary needed for exact Session reconstruction. Chat compares that full header with the preceding loaded Request Prompt and displays a non-empty system prompt only for the initial request, an explicit message-series start, or a real system-field change. An unchanged resume does not create a visible repetition.
|
||||
|
||||
A partial history window may begin with a non-initial header and lack the predecessor needed for comparison. Chat renders that system prompt conservatively. If prepend later supplies an identical predecessor, the existing request-prompt Node becomes hidden instead of being withdrawn; its key and page-lifetime anchor stay stable. A different system field remains visible.
|
||||
|
||||
Trajectory exposes every request header and its classified changes. Chat presentation does not alter provider requests, Session events, or reconstruction.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Omit unchanged resume headers from the Session log.** Rejected: resume is a real request boundary, and removing it would make exact reconstruction depend on process history that the durable log does not contain.
|
||||
|
||||
**Special-case only forked Sessions.** Rejected: an ordinary process resume has the same presentation semantics, and the request headers already contain the system fields needed for a direct comparison.
|
||||
|
||||
**Keep the duplicate row as a lifecycle marker.** Rejected: `System prompt` describes model-visible request content, so using it to mark a loop restart incorrectly implies another prompt injection. Request lifecycle evidence remains available in Trajectory.
|
||||
|
||||
## Consequences
|
||||
|
||||
Continuing a fork or resuming a process with an unchanged system field leaves one visible `System prompt` row for the current message series. Explicit series starts and real system changes still repeat the row. A partial window can initially show a conservative row and hide it after older history loads, while retaining the same materialized Node.
|
||||
|
||||
The unit regression covers initial, series, unchanged resume, system-change, and prepend cases. The Web recorded-session scenario contains an unchanged resume header and asserts that the settled Chat renders exactly one `System prompt` control.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Agent Note: Resume header 不重复系统提示词
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-03-resume-headers-do-not-repeat-system-prompts.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
fork Session 会把源会话历史复制到子会话。即使 system 字段与前一条被复制的 header 完全相同,子会话的第一个模型请求仍会记录一条 reason 为 `resume` 的 `request/header`。Chat 把每条 resume header 都视作新的展示点,因此继续 fork 会显示第二行`系统提示词`,让人误以为系统提示词被注入了两次。提供方请求实际仍只携带一次 system 字段;重复仅存在于 Chat 展示中。
|
||||
|
||||
## 决策
|
||||
|
||||
持久化 resume header 记录精确重建 Session 所需的请求边界。Chat 会把完整 header 与前一条已加载 Request Prompt 比较,只在初始请求、显式消息序列起点或真实 system 字段变化时显示非空系统提示词。内容未变的 resume 不创建可见的重复行。
|
||||
|
||||
部分历史窗口可能以非初始 header 开头,因缺少前序 header 而无法比较。Chat 会保守渲染该系统提示词。如果 prepend 随后补入相同的前序 header,既有 request-prompt Node 会转为隐藏而不是被撤回;其 key 和页面生命周期内的 anchor 保持稳定。不同的 system 字段仍然可见。
|
||||
|
||||
Trajectory 会展示每一条请求 header 及其变化分类。Chat 展示不会改变提供方请求、Session event 或重建行为。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**从 Session log 省略未变化的 resume header。** 否决:resume 是真实的请求边界,移除后精确重建将依赖持久日志未记录的进程历史。
|
||||
|
||||
**只对 fork Session 做特殊处理。** 否决:普通进程恢复具有相同的展示语义,请求 header 已经包含可直接比较的 system 字段。
|
||||
|
||||
**把重复行保留为生命周期标记。** 否决:`系统提示词`描述模型可见的请求内容,用它标记 loop 重启会错误暗示再次注入提示词。请求生命周期证据仍可在 Trajectory 中查看。
|
||||
|
||||
## 后果
|
||||
|
||||
继续 fork 或在 system 字段未变时恢复进程,当前消息序列只保留一行可见的`系统提示词`。显式序列起点与真实 system 变化仍会重复该行。部分窗口起初可以显示保守行,并在更早历史加载后将其隐藏,同时保留同一个已物化 Node。
|
||||
|
||||
单元回归覆盖初始请求、显式序列、未变化 resume、system 变化与 prepend 场景。Web 录制会话场景包含一条未变化的 resume header,并断言稳定后的 Chat 只渲染一个`系统提示词`控件。
|
||||
@@ -173,6 +173,10 @@ describe('web e2e: long Chat interaction contract', () => {
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps heterogeneous rows and their actions bound to exact semantic identities', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-chat-long-interactions'))
|
||||
await expect.poll(
|
||||
() => scaffold.ctx.agents.get(SessionId(SESSION_ID)) !== undefined,
|
||||
{ timeout: 10_000 },
|
||||
).toBe(true)
|
||||
const source = scaffold.ctx.agents.get(SessionId(SESSION_ID))
|
||||
if (source === undefined) throw new Error('seeded long-history agent is not attached')
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Web e2e scenario: message IconActions + clocks. Cold-seeds a deterministic
|
||||
// completed-turn-tail fork case (zero model calls) and pins the settled
|
||||
// conversation aria after the footers are focus-revealed — the surface package
|
||||
// jsdom tests cannot substitute for (docs/testing.md snapshot rule).
|
||||
// completed-turn-tail fork case with an unchanged resume header (zero model
|
||||
// calls) and pins the settled conversation aria after the footers are
|
||||
// focus-revealed — the surface package jsdom tests cannot substitute for
|
||||
// (docs/testing.md snapshot rule).
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -70,6 +71,10 @@ function completedTailFixture(raw: string): string {
|
||||
},
|
||||
}
|
||||
})
|
||||
const inheritedHeader = kept.findLast(event => event.type === 'request/header')
|
||||
if (inheritedHeader?.type !== 'request/header') {
|
||||
throw new Error('borrowed recording has no request header')
|
||||
}
|
||||
let seq = (kept.at(-1)?.seq ?? -1) + 1
|
||||
let time = (kept.at(-1)?.time ?? -1) + 1
|
||||
const at = (event: Record<string, unknown>): { seq: number; time: number } & Record<string, unknown> => ({
|
||||
@@ -109,6 +114,7 @@ function completedTailFixture(raw: string): string {
|
||||
surfaceOp: 'append',
|
||||
}),
|
||||
at({ type: 'step/start', data: { turn: 2, step: 1 } }),
|
||||
at({ type: 'request/header', data: { header: inheritedHeader.data.header, reason: 'resume' } }),
|
||||
at({
|
||||
type: 'assistant/message',
|
||||
data: {
|
||||
@@ -149,6 +155,9 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
await writeFile(join(sessionCwd, 'b.txt'), 'beta\n')
|
||||
const raw = completedTailFixture(await readFile(SEED, 'utf8'))
|
||||
expect(fixtureUserPrompts(raw), 'adapted seed must carry both prompts').toEqual([PROMPT, SECOND_PROMPT])
|
||||
expect(parseSeedFixture(raw).events.flatMap(event => event.type === 'request/header'
|
||||
? [event.data.reason]
|
||||
: []), 'adapted seed must carry an unchanged resume header').toEqual(['initial', 'resume'])
|
||||
await seedSession(scaffold, raw, SEED_ID)
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
@@ -172,6 +181,10 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
await sessionRow.click()
|
||||
await expect.poll(() => page.getByText(MID_TURN_TEXT, { exact: true }).count(), { timeout: 15_000 }).toBe(1)
|
||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBe(1)
|
||||
await expect.poll(
|
||||
() => page.getByRole('button', { name: 'System prompt', exact: true }).count(),
|
||||
{ timeout: 10_000 },
|
||||
).toBe(1)
|
||||
|
||||
// Focus-reveal the footers (hover:hover keeps them opacity-hidden until
|
||||
// hover/focus-within). Branch renders only under assistant answers — user
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-chat/README.md
|
||||
README.md: 79214bf2feb8384cecbb152aa31af162dc6dfdde
|
||||
README.zh.md: 674959e1974c74abba75f179eff50fe8488458fc
|
||||
README.md: 5a557f2a2125bddfe18631359665d6fa5e998458
|
||||
README.zh.md: 2fc241ad71e35eb54afcf4dde38c361248848d5f
|
||||
|
||||
@@ -25,7 +25,7 @@ The browser Chat target for Conversation assembly. It registers Chat event defin
|
||||
<a id="system-prompt-row"></a>
|
||||
## System prompt row
|
||||
|
||||
Chat shows a collapsed `System prompt` row for each non-empty initial or resumed request, explicit message-series start, or real system-field change. It does not repeat the row for same-series config-only or tool-only changes, tool steps, or retries. The row appears before that request's user messages, matching the provider envelope, and expands to the exact model-visible text with its original line breaks. A partial history window renders a non-initial header conservatively until the preceding page arrives; a header without a system prompt creates no row.
|
||||
Chat shows a collapsed `System prompt` row for a non-empty initial request, explicit message-series start, real system-field change, or non-initial request whose preceding header is outside the loaded history window. Once that predecessor is available, an unchanged resume does not repeat the row; same-series config-only or tool-only changes, tool steps, and retries also create no repetition. The row appears before that request's user messages, matching the provider envelope, and expands to the exact model-visible text with its original line breaks. A header without a system prompt creates no row.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Conversation 组装的浏览器 Chat target。本包注册 Chat event definition
|
||||
<a id="system-prompt-row"></a>
|
||||
## 系统提示词行
|
||||
|
||||
Chat 会为每个非空的初始或恢复请求、显式消息序列起点或真实 system 字段变化显示一行默认折叠的`系统提示词`。同一序列内仅配置或仅工具变化、工具步骤与重试不会重复该行。该行位于请求的用户消息之前,与提供方 envelope 顺序一致;展开后显示保留原始换行的精确模型可见文本。历史窗口不完整时,非初始 header 会保守显示,直到前一页到达;没有系统提示词的 header 不创建该行。
|
||||
Chat 会为非空的初始请求、显式消息序列起点、真实 system 字段变化,或前序 header 尚未进入已加载历史窗口的非初始请求显示一行默认折叠的`系统提示词`。前序 header 到达后,内容未变的 resume 不会重复该行;同一序列内仅配置或仅工具变化、工具步骤与重试也不会重复。该行位于请求的用户消息之前,与提供方 envelope 顺序一致;展开后显示保留原始换行的精确模型可见文本。没有系统提示词的 header 不创建该行。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export function requestPromptDefinition(inspect: RequestPromptInspector): Conver
|
||||
match.event.data.reason === 'initial',
|
||||
),
|
||||
showsPrompt: previous === undefined
|
||||
|| match.event.data.reason !== 'change'
|
||||
|| match.event.data.reason === 'series'
|
||||
|| match.event.data.startsSeries === true
|
||||
|| change === 'system'
|
||||
|| change === 'system-and-tools',
|
||||
@@ -89,8 +89,17 @@ export function requestPromptDefinition(inspect: RequestPromptInspector): Conver
|
||||
update: context => context.state,
|
||||
buildViewNode: (context) => {
|
||||
const state = context.state
|
||||
if (state === undefined || !state.showsPrompt || state.prompt.system === '') return null
|
||||
return chatNode(context, 'system-prompt', state.anchorSeq, { text: state.prompt.system })
|
||||
if (state === undefined) return null
|
||||
const current = context.current.get('chat') as ChatNode | null | undefined
|
||||
const visible = state.showsPrompt && state.prompt.system !== ''
|
||||
if (!visible && current?.kind !== 'system-prompt') return null
|
||||
return chatNode(
|
||||
context,
|
||||
'system-prompt',
|
||||
state.anchorSeq,
|
||||
{ text: state.prompt.system },
|
||||
{ visibility: visible ? 'visible' : 'hidden' },
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1402,7 +1402,7 @@ describe('built-in conversation node Definitions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('materializes series starts and system changes but not same-series config or tool changes', () => {
|
||||
it('materializes series starts and system changes but not unchanged resumes, config, or tool changes', () => {
|
||||
const tools = [{ name: 'read', description: 'Read', parameters: { type: 'object' } }]
|
||||
const expandedTools = [...tools, { name: 'write', description: 'Write', parameters: { type: 'object' } }]
|
||||
const value = assembler([
|
||||
@@ -1458,7 +1458,6 @@ describe('built-in conversation node Definitions', () => {
|
||||
expect(prompts.map(prompt => ({ anchorSeq: prompt.anchorSeq, data: prompt.data }))).toEqual([
|
||||
{ anchorSeq: 1, data: { text: '# Initial' } },
|
||||
{ anchorSeq: 4, data: { text: '# Initial' } },
|
||||
{ anchorSeq: 5, data: { text: '# Initial' } },
|
||||
{ anchorSeq: 6, data: { text: '# Updated' } },
|
||||
])
|
||||
|
||||
@@ -1480,18 +1479,20 @@ describe('built-in conversation node Definitions', () => {
|
||||
windowed.prepend([
|
||||
at(5, 'request/header', {
|
||||
reason: 'initial',
|
||||
header: { config: { provider: 'fake', model: 'fake' }, system: '# Original prompt' },
|
||||
header: { config: { provider: 'fake', model: 'fake' }, system: '# Resumed prompt' },
|
||||
}),
|
||||
], false)
|
||||
windowed.flush()
|
||||
const restored = snapshot(windowed)
|
||||
const restoredPrompts = restored.order.flatMap((key) => {
|
||||
const candidate = restored.nodes.get(key)
|
||||
return candidate?.kind === 'system-prompt' ? [candidate] : []
|
||||
})
|
||||
expect(restoredPrompts.map(prompt => prompt.data)).toEqual([
|
||||
{ text: '# Original prompt' },
|
||||
{ text: '# Resumed prompt' },
|
||||
const restoredPrompts = restored.nodes.values()
|
||||
.filter(candidate => candidate.kind === 'system-prompt')
|
||||
expect(restoredPrompts.map(prompt => ({
|
||||
anchorSeq: prompt.anchorSeq,
|
||||
visibility: prompt.visibility,
|
||||
data: prompt.data,
|
||||
})).sort((left, right) => left.anchorSeq - right.anchorSeq)).toEqual([
|
||||
{ anchorSeq: 5, visibility: 'visible', data: { text: '# Resumed prompt' } },
|
||||
{ anchorSeq: 10, visibility: 'hidden', data: { text: '# Resumed prompt' } },
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -47,12 +47,7 @@
|
||||
- button "Ran for {{duration}}":
|
||||
- img
|
||||
- text: Ran for {{duration}}
|
||||
- text: {{clock}}
|
||||
- button "System prompt":
|
||||
- img
|
||||
- img
|
||||
- text: System prompt
|
||||
- text: Now give the same explanation to a human reader. {{clock}}
|
||||
- text: {{clock}} Now give the same explanation to a human reader. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Thought for a while" [expanded]:
|
||||
|
||||
@@ -39,12 +39,7 @@
|
||||
- button "Ran for {{duration}}":
|
||||
- img
|
||||
- text: Ran for {{duration}}
|
||||
- text: {{clock}}
|
||||
- button "System prompt":
|
||||
- img
|
||||
- img
|
||||
- text: System prompt
|
||||
- text: Now give the same explanation to a human reader. {{clock}}
|
||||
- text: {{clock}} Now give the same explanation to a human reader. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Thought for a while":
|
||||
|
||||
Reference in New Issue
Block a user