mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
* refactor(ai-vault): move the surrogate-safe slice to shared, one implementation `sliceAtCodeUnitLimit` lived in src/main/ai-vault, and src/shared never imports src/main, so a shared consumer could not reach it. Rather than add a second copy, it moves to src/shared and ai-vault imports and re-exports it, leaving every existing importer of that module untouched. Separated from the feature that needs it: this is the only change here to a subsystem the rest of the branch does not touch. * feat(native-chat): give an agent session one place to hold its name Adds the normalized conversation name and the record field that stores it, so Orca has a durable note that it already named a session and does not name it again on a later acquisition. No name is generated yet, and nothing displays this field: the AI Vault path stays the home for the name a user sees. - `agent-session-conversation-name` bounds and flattens the text once, at 200 characters, cutting on a character boundary. - `AgentSessionRecord.conversationName` carries it, validated on load. - `setAgentSessionRecordConversationName` sets or clears it, unfenced: the name is a durable note, not ownership, so writing it never contends with the writer lease. * refactor(runtime): move reserve-owner orchestration next to its admission logic Pure move, no behavior change. `reserveOwner`'s transaction body sequenced decisions that all live in agent-session-reservation-admission and then applied the winning one; it now sits there as `commitAgentSessionReservation`, and the store keeps the transaction boundary and a one-line delegation. Takes agent-session-record-store.ts from 300/300 to 279/300, which is what the conversation-name field needs to land. Every existing test passes unedited. The module header said nothing in it mutates; that is now qualified rather than left false, since the committer writes the state it is handed. * feat(runtime): let the store set a session's conversation name `setConversationName` is the one writer for the record field, so a producer never reaches the record shape itself. It normalizes at the boundary, so no caller can persist a name the loader would then reject as unreadable. Unfenced by design: the name is durable memory that Orca already named this session, not ownership, so naming never contends with the writer lease. No name generation and no provider code yet. * refactor(runtime): reuse the shared default chat label on a replacement tab The replacement tab open-coded `'Claude Chat' : 'Codex Chat'` while every other publisher already calls `defaultAgentChatLabel`. One writer for the placeholder. * fix(native-chat): reject noncanonical stored conversation names --------- Co-authored-by: Merge Sim <sim@local>
20 lines
771 B
TypeScript
20 lines
771 B
TypeScript
// Cutting text to a length bound without splitting a character in half.
|
|
//
|
|
// JavaScript string length counts UTF-16 code units, so a raw `slice` at a
|
|
// bound can land between the two halves of an astral character — an emoji, or
|
|
// most CJK extension characters — and leave a lone surrogate that every surface
|
|
// renders as U+FFFD.
|
|
|
|
/** Cut to `limit` UTF-16 code units without splitting a trailing surrogate pair. */
|
|
export function sliceAtCodeUnitLimit(value: string, limit: number): string {
|
|
if (value.length <= limit) {
|
|
return value
|
|
}
|
|
const end = limit > 0 && isHighSurrogate(value.charCodeAt(limit - 1)) ? limit - 1 : limit
|
|
return value.slice(0, end)
|
|
}
|
|
|
|
function isHighSurrogate(code: number): boolean {
|
|
return code >= 0xd800 && code <= 0xdbff
|
|
}
|