mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 08:02:31 +00:00
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
93 lines
3.6 KiB
TypeScript
93 lines
3.6 KiB
TypeScript
// Why: agent harnesses (Claude Code and its forks) inject machinery into the
|
|
// conversation as user-role turns — background task notifications, system
|
|
// reminders, inter-agent messages, slash-command envelopes, local-command
|
|
// output, interruption and compaction notices. These fire user-prompt hooks
|
|
// and land in transcripts, but they are not something the user typed, so
|
|
// prompt-derived UI must not surface them.
|
|
//
|
|
// We match only tags we have observed from harnesses, never a broad kebab
|
|
// shape: a real prompt starting with a custom `<my-element>` or a Grok
|
|
// `<user_query>` envelope is a genuine user turn, and misclassifying it would
|
|
// hide the turn (drop it from transcripts, demote its session title, or leave
|
|
// the agent visibly done after an interrupt).
|
|
const LEADING_TAG_NAME = /^<([a-z][a-z0-9-]*)(?:[\s>]|$)/
|
|
|
|
// Consumers must only treat tags we have observed from harnesses as machinery;
|
|
// arbitrary kebab tags can be genuine user code.
|
|
const KNOWN_HARNESS_TAG_NAMES = new Set([
|
|
'agent-message',
|
|
'bash-input',
|
|
'bash-stderr',
|
|
'bash-stdout',
|
|
'command-args',
|
|
'command-message',
|
|
'command-name',
|
|
'cross-session-message',
|
|
'fork-boilerplate',
|
|
'local-command-caveat',
|
|
'local-command-stderr',
|
|
'local-command-stdout',
|
|
'mcp-polling-update',
|
|
'mcp-resource-update',
|
|
'system-reminder',
|
|
'task-notification',
|
|
'teammate-message',
|
|
'user-memory-input',
|
|
'user-prompt-submit-hook'
|
|
])
|
|
|
|
// Injected turns identified by a leading string rather than a known tag name:
|
|
// the harness only emits <channel> in its attributed `<channel source=…>` form
|
|
// (a bare <channel> is a real RSS/XML paste), plus prose deliveries and notices.
|
|
const COMPACT_CONTINUATION_PREFIX = 'this session is being continued from a previous conversation'
|
|
const HARNESS_INJECTED_TURN_PREFIXES = [
|
|
'<channel source=',
|
|
'[request interrupted',
|
|
'a message arrived from ',
|
|
'another claude session sent a message',
|
|
'no response requested.',
|
|
'caveat: the messages below were generated by the user while running local commands',
|
|
COMPACT_CONTINUATION_PREFIX
|
|
]
|
|
|
|
// Why: classification only inspects leading tags/prefixes. Cap the toLowerCase
|
|
// copy so vault-scan / prompt-seed paths stay O(1) on multi-KB pastes.
|
|
const HARNESS_CLASSIFY_HEAD_LIMIT = 256
|
|
const HARNESS_CLASSIFY_LEADING_WS_LIMIT = 64
|
|
|
|
/** True only for observed harness shapes. Match on trimmed, lowercased text.
|
|
* Unknown kebab tags stay user turns — only tags we have observed count. */
|
|
export function isKnownHarnessInjectedUserTurnText(text: string): boolean {
|
|
const normalized = normalizedHarnessTurnHead(text)
|
|
if (!normalized) {
|
|
return false
|
|
}
|
|
const tagName = LEADING_TAG_NAME.exec(normalized)?.[1]
|
|
if (tagName && KNOWN_HARNESS_TAG_NAMES.has(tagName)) {
|
|
return true
|
|
}
|
|
return HARNESS_INJECTED_TURN_PREFIXES.some((prefix) => normalized.startsWith(prefix))
|
|
}
|
|
|
|
/** True only for the observed post-compaction continuation prompt. */
|
|
export function isCompactContinuationUserTurnText(text: string): boolean {
|
|
return normalizedHarnessTurnHead(text).startsWith(COMPACT_CONTINUATION_PREFIX)
|
|
}
|
|
|
|
function normalizedHarnessTurnHead(text: string): string {
|
|
let start = 0
|
|
const wsScanEnd = Math.min(text.length, HARNESS_CLASSIFY_LEADING_WS_LIMIT)
|
|
while (start < wsScanEnd && isAsciiWhitespace(text.charCodeAt(start))) {
|
|
start += 1
|
|
}
|
|
if (start >= text.length) {
|
|
return ''
|
|
}
|
|
const headEnd = Math.min(text.length, start + HARNESS_CLASSIFY_HEAD_LIMIT)
|
|
return text.slice(start, headEnd).toLowerCase()
|
|
}
|
|
|
|
function isAsciiWhitespace(code: number): boolean {
|
|
return code === 32 || code === 9 || code === 10 || code === 13 || code === 12
|
|
}
|