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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
owner, immutable request snapshots remain enforced at AgentRun acceptance and backend dispatch,
and SessionEvent-to-RuntimeEvent conversion remains a pure mapper.
- Retired the Task Ledger domain: SessionTodo is now the sole authority for in-session work items, and the operational-state schema drops the `workflow_task_ledger_events` table on first open. **Unfinished Tasks are not migrated and are permanently deleted.** This affects workspaces last opened by `v0.1.0` through `v0.1.11`, `cli-v0.1.0-beta.1`, `v0.2.0-incubating-rc1`, or a `v0.2.0-dev` build; those releases wrote Tasks to a table that no shipped build ever bridged into SessionTodo. Before opening such a workspace with this build, finish or export the Tasks you still need, or copy the workspace's `runtime.sqlite` aside — the migration removes the only live copy, so afterwards recovery requires a backup made in advance.
- Let the provider decide whether a request fits, and anchored the estimate that decides when to compact on the last request the provider actually counted. `token_usage` records now persist that anchor under a new `lastRequestAnchor` key. **Sessions this build writes do not open in earlier releases:** those decode `token_usage` against a closed allowlist, so the unknown key fails the record and, with it, the Session that contains it. Downgrading therefore needs a copy of the workspace's `runtime.sqlite` taken before the upgrade. Retired with the local verdict: nothing produces the `context_budget_exhausted` stop reason any more — a request that really is too large is compacted and retried once, then reported as a `context_overflow` provider error — though sessions that already recorded it still decode and present. The Runtime Host compatibility epoch moves to 94.
- Unified context management under one Runtime-owned policy. `MAKA_CONTEXT_*` environment overrides no longer tune or disable compaction and Tool Result pruning; model-visible archive placeholders are read on demand through bounded `ArchiveRead` calls instead of eager hydration. Previously supported overrides are ignored on upgrade: if Tool Result pruning was set to `off`, pruning is re-enabled, and there is currently no supported replacement opt-out.

## 0.1.11 - 2026-08-18
Expand Down
17 changes: 0 additions & 17 deletions apps/desktop/src/main/__tests__/session-error-presentation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,3 @@ describe('provider capacity presentation', () => {
});
});

describe('context compaction failure presentation', () => {
it('shows actionable malformed-summary guidance', () => {
const message = sessionEventErrorMessage({
type: 'error',
id: 'error-1',
turnId: 'turn-1',
ts: 1,
recoverable: false,
reason: 'context_budget_exhausted',
message: 'Turn failed: context_budget_exhausted',
details: { contextBudgetExhaustedDetail: 'malformed_summary_missing_section' },
});

assert.match(message, /上下文压缩/);
assert.match(message, /上下文窗口设置|切换模型|开启新任务/);
});
});
6 changes: 2 additions & 4 deletions apps/desktop/src/renderer/locales/conversation-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,6 @@ export interface DesktopConversationCopy {
turnError: {
unknown: string;
contextOverflow: string;
contextBudgetExhausted: string;
malformedSummary: string;
timeout: string;
auth: string;
providerBilling: string;
Expand Down Expand Up @@ -658,7 +656,7 @@ const COPY = {
reauth: { label: '上次连接测试鉴权失败', tooltip: '最近一次连接测试返回鉴权失败(401 / 403),密钥可能已过期或被吊销。这不会拦截发送,但若发送失败请到 设置 · 模型 重新登录。' },
testError: { label: '上次连接测试失败', tooltip: '最近一次连接测试因网络 / 超时 / 5xx 失败。这不会拦截发送,但若问题持续请到 设置 · 模型 检查 Base URL / 代理。' },
},
turnError: { unknown: '出错了,原因不明。重新发消息重试。', contextOverflow: '上下文超出模型窗口限制,减少附件或开启新任务。', contextBudgetExhausted: '上下文已达上限,这个任务无法继续。换模型或开启新任务。', malformedSummary: '上下文压缩未能生成有效摘要。请检查模型的上下文窗口设置、切换模型,或开启新任务。', timeout: '模型请求超时,重新发消息重试。', auth: '模型鉴权失败,请到设置里重新连接或登录。', providerBilling: '模型服务计费受限,请检查账号余额或订阅状态。', providerCapacity: '模型服务暂时满载,等几分钟重试,或换一个模型。', rateLimit: '模型请求太频繁被限流了,等一会儿再发消息重试。', network: '网络连接失败,检查网络后重新发消息。', provider: '模型服务返回错误,稍后重试或换一个模型。', stepCap: '达到工具调用步数上限,任务可能没做完。发消息让它继续。', tool: '工具调用失败,看一下上面的工具结果再决定要不要重试。', permission: '这一轮在等权限确认时结束了,重新发消息会再问一次。', restarted: '本地应用重启,上一轮没有完成', sandboxBoundaryClosed: '本地应用重启时,等待确认的「允许访问工作区以外的内容」请求已按拒绝关闭。重新发消息可以再决定一次。', executionState: { erroredTool: '这一轮有工具执行出错,先看它的结果,再决定要不要重发。', toolRan: '这一轮已经执行过工具,可能已经产生实际改动,重发前先看工具结果。', partialOutput: '这一轮已经产生了部分回答,重发前可以先看看。' } },
turnError: { unknown: '出错了,原因不明。重新发消息重试。', contextOverflow: '上下文超出模型窗口限制,减少附件或开启新任务。', timeout: '模型请求超时,重新发消息重试。', auth: '模型鉴权失败,请到设置里重新连接或登录。', providerBilling: '模型服务计费受限,请检查账号余额或订阅状态。', providerCapacity: '模型服务暂时满载,等几分钟重试,或换一个模型。', rateLimit: '模型请求太频繁被限流了,等一会儿再发消息重试。', network: '网络连接失败,检查网络后重新发消息。', provider: '模型服务返回错误,稍后重试或换一个模型。', stepCap: '达到工具调用步数上限,任务可能没做完。发消息让它继续。', tool: '工具调用失败,看一下上面的工具结果再决定要不要重试。', permission: '这一轮在等权限确认时结束了,重新发消息会再问一次。', restarted: '本地应用重启,上一轮没有完成', sandboxBoundaryClosed: '本地应用重启时,等待确认的「允许访问工作区以外的内容」请求已按拒绝关闭。重新发消息可以再决定一次。', executionState: { erroredTool: '这一轮有工具执行出错,先看它的结果,再决定要不要重发。', toolRan: '这一轮已经执行过工具,可能已经产生实际改动,重发前先看工具结果。', partialOutput: '这一轮已经产生了部分回答,重发前可以先看看。' } },
},
en: {
actions: { stopFailedTitle: 'Failed to stop', stopFailedFallback: 'The task action failed. Try again later.', refreshSessionsFailedTitle: 'Failed to refresh tasks', refreshSessionsFailedFallback: 'The task list could not be refreshed. Try again later.', conversationErrorTitle: 'Task error', conversationErrorFallback: 'The task run failed. Try again later.', regenerateStartedTitle: 'Regeneration started', regenerateStartedDescription: 'Generating a new response', branchCreatedTitle: 'Branch created', branchCreatedDescription: (name) => `New task: ${name}`, revisionStartedTitle: 'Edit draft ready', revisionStartedDescription: 'The original task is kept; sending creates a new version', revisionReadyTitle: 'Ready to edit and resend', revisionReadyDescription: 'Rewound to before that message; edit and send when ready', revisionUnavailableTitle: 'This message cannot be edited yet', revisionAttachmentsUnsupported: 'Edit & resend does not yet support historical attachments. Copy the text into a new message instead.', revisionTransformedTextUnsupported: 'Edit & resend does not yet support messages sent with an explicit skill. Copy the text and select the skill again instead.', revisionDraftAttachmentConflict: 'The composer already has pending attachments. Send or remove them before editing a sent message.', revisionCommandUnsupported: 'You cannot run /compact, /side, or orchestration commands while editing a sent message. Cancel the edit first.', revisionAlreadyActive: 'Another message is already being edited. Send or cancel that edit first.', revisionCancelLabel: 'Cancel', revisionBannerTitle: 'Editing sent message', revisionBannerDetail: '· New version on send', revisionUnchanged: 'Nothing changed. Use Regenerate if you only want a new answer.', operationFailedTitle: 'Action failed', operationFailedFallback: 'The task action failed. Try again later.', attachmentFailedTitle: 'Failed to add attachment', imageAttachmentNotDirectTitle: 'Image added as an attachment', imageAttachmentNotDirectDescription: 'The current model does not receive images directly. The image has been provided as an attachment.', tryAgain: 'Try again later.', modelReboundTitle: 'Switched to an available model', modelReboundDescription: (modelId) => `The previous connection is unavailable${modelId ? ` · ${modelId}` : ''}`, messageReadFailedTitle: 'Failed to load task', partialHistoryTitle: 'Viewing earlier messages', returnLatest: 'Return to latest', scrollMainToBottom: 'Scroll main conversation to bottom' },
Expand Down Expand Up @@ -894,7 +892,7 @@ const COPY = {
reauth: { label: 'Last connection test failed authentication', tooltip: 'The latest test returned 401 / 403. Sending is not blocked, but sign in again under Settings · Models if it fails.' },
testError: { label: 'Last connection test failed', tooltip: 'The latest test failed because of a network, timeout, or 5xx error. Sending is not blocked; check Base URL or proxy settings if it persists.' },
},
turnError: { unknown: 'Something went wrong, cause unknown. Send a message to retry.', contextOverflow: 'Context exceeded the model window. Reduce attachments or start a new task.', contextBudgetExhausted: 'The context limit was reached and this task cannot continue. Switch models or start a new task.', malformedSummary: 'Context compaction could not produce a valid summary. Check the model context-window setting, switch models, or start a new task.', timeout: 'The model request timed out. Send a message to retry.', auth: 'Model authentication failed. Reconnect or sign in again from Settings.', providerBilling: 'Model billing is restricted. Check the account balance or subscription.', providerCapacity: 'The model service is temporarily at capacity. Wait a few minutes, or switch models.', rateLimit: 'Requests were rate-limited. Wait a moment, then send a message to retry.', network: 'The network connection failed. Check the network, then send a message again.', provider: 'The model service returned an error. Retry later, or switch models.', stepCap: 'The tool-step limit was reached, so the task may be incomplete. Send a message to continue.', tool: 'A tool call failed. Check the tool result above before deciding whether to retry.', permission: 'This turn ended while waiting for permission. Send a message and it will ask again.', restarted: 'The app restarted before the previous turn completed', sandboxBoundaryClosed: 'The app restarted, so the pending request to reach outside the workspace was closed as denied. Send a message to decide again.', executionState: { erroredTool: 'A tool errored during this turn. Read its result before deciding whether to send another message.', toolRan: 'Tools already ran during this turn and may have made real changes. Read their results before sending another message.', partialOutput: 'This turn produced part of an answer. Worth reading before you send another message.' } },
turnError: { unknown: 'Something went wrong, cause unknown. Send a message to retry.', contextOverflow: 'Context exceeded the model window. Reduce attachments or start a new task.', timeout: 'The model request timed out. Send a message to retry.', auth: 'Model authentication failed. Reconnect or sign in again from Settings.', providerBilling: 'Model billing is restricted. Check the account balance or subscription.', providerCapacity: 'The model service is temporarily at capacity. Wait a few minutes, or switch models.', rateLimit: 'Requests were rate-limited. Wait a moment, then send a message to retry.', network: 'The network connection failed. Check the network, then send a message again.', provider: 'The model service returned an error. Retry later, or switch models.', stepCap: 'The tool-step limit was reached, so the task may be incomplete. Send a message to continue.', tool: 'A tool call failed. Check the tool result above before deciding whether to retry.', permission: 'This turn ended while waiting for permission. Send a message and it will ask again.', restarted: 'The app restarted before the previous turn completed', sandboxBoundaryClosed: 'The app restarted, so the pending request to reach outside the workspace was closed as denied. Send a message to decide again.', executionState: { erroredTool: 'A tool errored during this turn. Read its result before deciding whether to send another message.', toolRan: 'Tools already ran during this turn and may have made real changes. Read their results before sending another message.', partialOutput: 'This turn produced part of an answer. Worth reading before you send another message.' } },
},
} satisfies UiCatalog<DesktopConversationCopy>;

Expand Down
7 changes: 0 additions & 7 deletions apps/desktop/src/renderer/model-connection-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,6 @@ export function sessionEventErrorMessage(
if (isNoRealConnectionEvent(event)) {
return noRealConnectionSetupDescription(noRealConnectionReasonFromEvent(event), locale);
}
const contextBudgetDetail =
event.details && !Array.isArray(event.details)
? event.details.contextBudgetExhaustedDetail
: undefined;
if (typeof contextBudgetDetail === 'string' && contextBudgetDetail.startsWith('malformed_summary_')) {
return getDesktopConversationCopy(locale).turnError.malformedSummary;
}
const reasonDescription = describeSessionErrorReason(event.reason, locale);
if (reasonDescription) return reasonDescription;
const fallback = getDesktopConversationCopy(locale).actions.conversationErrorFallback;
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/renderer/session-error-presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export function describeSessionErrorReason(reason: string | undefined, locale: U
switch (reason?.toLowerCase()) {
case 'context_overflow':
return copy.contextOverflow;
case 'context_budget_exhausted':
return copy.contextBudgetExhausted;
case 'timeout':
return copy.timeout;
case 'model_after_tool_timeout':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Third, the checkpoint is not itself a canonical RuntimeEvent. Coverage and tail

## Triggering ends before compaction begins

Runtime derives one capacity from the selected model's metadata. For a known context window it reserves one quarter of the window, capped at 16,384 tokens; most providers without a known window use a 32,000-token history budget plus the classic 16,384-token reserve.
Capacity is the selected model's declared context window or nothing at all; Runtime never manufactures one. For a declared window it reserves one quarter of the window, capped at 16,384 tokens, and shapes history against the rest. Where no window is declared there is no capacity to weigh a request against, and the pre-turn gate falls back to the policy's own history-shaping budget — 32,000 tokens for most providers, and none where the provider publishes neither. Either way an estimate only asks for compaction; it never ends a request.

Trigger owners use that capacity but do not participate in compaction:

Expand Down Expand Up @@ -393,7 +393,7 @@ Compaction crosses token estimation, an LLM call, schema construction, durable a
| Failure point | Current behavior | What must not happen |
|---|---|---|
| Below high water | Keep the existing projection or apply ordinary budget selection | Create an unsourced summary as a speculative optimization |
| LLM returns an empty summary | Record no new checkpoint. Automatic pre-turn compaction keeps the original source-derived projection and, if it remains over budget, terminates with `context_budget_exhausted` without writing a failure note; manual compaction records one visible `context_compaction_failed_open` note | Treat an empty projection as covered history |
| LLM returns an empty summary | Record no new checkpoint. Automatic pre-turn compaction keeps the original source-derived projection and dispatches it without writing a failure note, leaving the provider to say whether it fits; manual compaction records one visible `context_compaction_failed_open` note | Treat an empty projection as covered history |
| Text summary is malformed | Spend one stricter repair attempt, then fail open with a granular reason; do not redispatch an unchanged failed fingerprint | Persist incomplete structure or loop on the same doomed compaction input |
| Codex returns no unique valid compact item | Try one portable text-summary checkpoint, then fail open if that also fails | Persist partial or ambiguous provider state |
| Native compaction input cannot fit after bounded Tool Result omission | Do not dispatch the native request; try one bounded text-summary checkpoint | Ask the provider to compact an already over-capacity request |
Expand All @@ -404,7 +404,7 @@ Compaction crosses token estimation, an LLM call, schema construction, durable a
| Bounded projection is damaged | Recover from canonical AgentRun ledgers and repair the projection | Treat the cache as the only source of truth |
| User stops manual compaction | Abort the summarizer/write path without poisoning the next Turn | Persist a late result or reuse aborted state |

Fail-open here does not mean “always send the complete raw history.” Once history exceeds the model budget, the full raw prefix may itself be impossible to send. An automatic pre-turn initial V2 summary failure leaves the original source-derived projection untouched; if that projection still exceeds the budget, the backend terminates with `context_budget_exhausted` before the failure-note path. Manual compaction records one visible `context_compaction_failed_open` note for the same failed outcome. A rolling failure may reuse the old checkpoint, but it never expands that checkpoint's coverage claim.
Fail-open here does not mean “always send the complete raw history.” Once history exceeds the model budget, the full raw prefix may itself be impossible to send. An automatic pre-turn initial V2 summary failure leaves the original source-derived projection untouched, and whether that projection still fits is the provider's answer: a rejected request is compacted and retried once, and a second rejection surfaces as a `context_overflow` provider error. Manual compaction records one visible `context_compaction_failed_open` note for the same failed outcome. A rolling failure may reuse the old checkpoint, but it never expands that checkpoint's coverage claim.

The correct interpretation is:

Expand Down
Loading