Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Merge pull request #364 from flagon-io/363-session-prompt-noise

A session's page shows what you asked, not what Claude Code injected; highlights leave out acknowledgements

syntaqxcommitted Parents249b1c8e30f733Browse files
6 files+72−140/6 viewed
+21−0
55
66 import {
77 ENDED_BY_HAND,
8+ askedText,
9+ isAcknowledgement,
810 checkHref,
911 endReasonText,
1012 evidenceOf,
462464 assert.equal(href(99), null, "unknown");
463465 assert.equal(href(50), null, "a deleted repository");
464466 });
467+
468+test("what someone asked leaves out what the harness or the editor put ahead of it", () => {
469+ const injected = "<browser_instruction># Claude in Chrome browser automation\n\nYou have access to browser tools.</browser_instruction>";
470+ assert.equal(askedText(`${injected}Alright, what's next on our work items?`), "Alright, what's next on our work items?");
471+ assert.equal(
472+ askedText("<system-reminder>Codebase instructions</system-reminder>\n<ide_selection>a.ts</ide_selection>\nWhy is the page crashing?"),
473+ "Why is the page crashing?",
474+ );
475+ // Only the blocks: a prompt that was nothing else asks nothing.
476+ assert.equal(askedText(injected), "");
477+ // A tag someone types is theirs.
478+ assert.equal(askedText("<div> renders twice, why?"), "<div> renders twice, why?");
479+ assert.equal(intentTitle(`${injected}Fix the sidebar under the app bar`), "Fix the sidebar under the app bar");
480+});
481+
482+test("acknowledging coordination updates is told apart from a note", () => {
483+ assert.ok(isAcknowledgement("Coordination updates upd_c62316db756f071acf8fa1bbff: Unrelated session ended"));
484+ assert.ok(!isAcknowledgement("The approvals were declined because nobody watched."));
485+});
+27−5
117117 /** A turn the harness starts itself, as a tag: `<command-name>`, `<task-notification>`, one with attributes. */
118118 const HARNESS_TAG = /^\s*<[a-z][\w-]*(?:\s[^>]*)?>/i;
119119
120−/** What an editor adds ahead of a prompt, a whole block at a time: `<ide_opened_file>…</ide_opened_file>`. */
121−const IDE_CONTEXT = /^\s*<(ide_[a-z_]+|system-reminder)\b[^>]*>[\s\S]*?<\/\1>/i;
120+/**
121+ * What a harness or an editor adds ahead of a prompt, a whole block at a
122+ * time: `<ide_opened_file>…</ide_opened_file>`, `<system-reminder>…`,
123+ * Claude Code's `<browser_instruction>…`. Their names have an underscore
124+ * or a hyphen, which a tag someone types (`<div>`) does not.
125+ */
126+const INJECTED = /^\s*<([a-z][a-z0-9]*[_-][a-z0-9_-]*)\b[^>]*>[\s\S]*?<\/\1\s*>/i;
127+
128+/**
129+ * What someone asked, as they typed it: the prompt without the blocks a
130+ * harness or an editor put ahead of it. Empty when the prompt was only
131+ * those blocks.
132+ */
133+export function askedText(prompt: string | null | undefined): string {
134+ let text = String(prompt ?? "").replace(/^\uFEFF/, "");
135+ while (INJECTED.test(text)) text = text.replace(INJECTED, "");
136+ return text.trim();
137+}
138+
139+/** A note `g1t session acknowledge` keeps for the coordination updates it took in: bookkeeping, not a highlight. */
140+export function isAcknowledgement(text: string): boolean {
141+ return /^Coordination updates? upd_/.test(text.trim());
142+}
122143
123144 /**
124145 * What a prompt is for, as a session's title: its first line or sentence,
134155 // pasted text (Claude Code's <pasted_content id="…">…</pasted_content>)
135156 // and image placeholders ([Image #1], [Image: source: …]) go first.
136157 text = text.replace(/<pasted_content\b[^>]*>[\s\S]*?(?:<\/pasted_content[^>]*>|$)/gi, "\n").replace(/\[Image(?: #\d+)?(?::[^\]\n]*)?\]/g, " ");
137− // Context an editor puts before what was typed (VS Code's
138− // <ide_opened_file>, <ide_selection>) is not a turn the harness started.
139− while (IDE_CONTEXT.test(text)) text = text.replace(IDE_CONTEXT, "");
158+ // Context a harness or an editor puts before what was typed (VS Code's
159+ // <ide_opened_file>, Claude Code's <browser_instruction>) is not a turn
160+ // the harness started.
161+ while (INJECTED.test(text)) text = text.replace(INJECTED, "");
140162 if (/^\s*\//.test(text) || HARNESS_TAG.test(text)) return null;
141163 text = text
142164 .replace(/```[\s\S]*?(```|$)/g, "\n")
+2−0
7070 entry(5, "note", "The approvals were declined because nobody watched.", "2026-10-21T10:01:00Z"),
7171 entry(6, "note", "", "2026-10-21T10:03:00Z"),
7272 entry(8, "message", "Not a highlight.", "2026-10-21T10:03:30Z"),
73+ // Acknowledging coordination updates is bookkeeping.
74+ entry(9, "note", "Coordination updates upd_c62316db756f071acf8fa1bbff: Unrelated session ended", "2026-10-21T10:03:40Z"),
7375 ];
7476 const where = (d: Decision) => (d.pull != null ? `/acme/web/pull/${d.pull}` : `/acme/-/work/${d.item}`);
7577 assert.deepEqual(highlights(decisions, entries, new Set(["ses_a", "ses_sub"]), where), [
+4−1
88 */
99 import type { Decision, LinkVia, SessionEntry, SessionLink, SessionTouch, WorkSession } from "@g1t/contracts";
1010
11+import { isAcknowledgement } from "./coordination.ts";
12+
1113 /**
1214 * What the session came to, in its own words: what it reported, else, once
1315 * it has ended, the last thing it said. Null while it is still going and
7880 return to ? [{ text: decision.summary, at: decision.createdAt, cite: { kind: "decision", to } }] : [];
7981 });
8082 const notes = entries
81− .filter((entry) => entry.kind === "note" && !entry.withheld && entry.text.trim() !== "")
83+ // Acknowledging coordination updates is bookkeeping, not something the session did.
84+ .filter((entry) => entry.kind === "note" && !entry.withheld && entry.text.trim() !== "" && !isAcknowledgement(entry.text))
8285 .map((entry): Highlight => ({ text: entry.text.trim(), at: entry.at, cite: { kind: "entry", seq: entry.seq } }));
8386 // One decision recorded twice (on the pull request and its work item) is one highlight.
8487 const seen = new Set<string>();
+5−3
2525 import {
2626 type SessionNode,
2727 TOUCH_KINDS,
28+ askedText,
2829 duration,
2930 endReasonText,
3031 harnessName,
520521 }, []);
521522
522523 const tools = entries.filter((entry) => entry.kind === "tool_call").length;
523− const prompt = entries.find((entry) => entry.kind === "prompt");
524+ // What it was asked: the first prompt with words of its own, without what the harness put ahead of them.
525+ const prompt = entries.find((entry) => entry.kind === "prompt" && (entry.withheld || askedText(entry.text) !== ""));
524526 const firstPrompt = prompt?.text;
525527 const isSubagent = Boolean(session.parentId);
526528 const parent = isSubagent && root?.id === session.parentId ? root : null;
788790
789791 {/* The story: what it was asked, how long it worked, and what it came to. */}
790792 <div className="mt-8 space-y-5">
791− {prompt && !prompt.withheld && prompt.text.trim() && <AskedBubble text={prompt.text} repo={repoAt(base)} />}
792− {isSubagent && session.prompt && (!prompt || prompt.withheld) && <AskedBubble text={session.prompt} repo={repoAt(base)} />}
793+ {prompt && !prompt.withheld && <AskedBubble text={askedText(prompt.text)} repo={repoAt(base)} />}
794+ {isSubagent && session.prompt && (!prompt || prompt.withheld) && <AskedBubble text={askedText(session.prompt)} repo={repoAt(base)} />}
793795 <WorkedFor live={live} span={duration(session, now)} tools={tools} open={transcript} onToggle={() => setTranscript((open) => !open)} />
794796 {answer ? (
795797 <SessionAnswer text={answer} repo={repoAt(base)} />
+13−5
239239 false
240240 }
241241
242−/// Context an editor puts before what was typed, a whole block at a time.
242+/// Context a harness or an editor puts before what was typed, a whole
243+/// block at a time: `<ide_opened_file>`, `<system-reminder>`, Claude Code's
244+/// `<browser_instruction>`. Their names have an underscore or a hyphen,
245+/// which a tag someone types (`<div>`) does not.
243246 fn drop_leading_context(mut text: String) -> String {
244247 loop {
245248 let trimmed = text.trim_start();
246249 let lower = trimmed.to_ascii_lowercase();
247− let name: String = if lower.starts_with("<ide_") || lower.starts_with("<system-reminder") {
248− lower[1..].chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '-').collect()
249− } else {
250+ let Some(after) = lower.strip_prefix('<') else { return text };
251+ let name: String = after.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '-').collect();
252+ if !name.starts_with(|c: char| c.is_ascii_alphabetic()) || !name.contains(['_', '-']) {
250253 return text;
251− };
254+ }
252255 let close = format!("</{name}>");
253256 match lower.find(&close) {
254257 Some(end) => text = trimmed[end + close.len()..].to_owned(),
11301133 );
11311134 assert_eq!(intent_title("<ide_selection>const a = 1;</ide_selection>\n<ide_opened_file>a.ts</ide_opened_file>\nrename this").as_deref(), Some("Rename this"));
11321135 assert_eq!(intent_title("<ide_opened_file>a.ts</ide_opened_file>\n/clear"), None);
1136+ // What Claude Code injects ahead of the prompt is not what it is for.
1137+ assert_eq!(
1138+ intent_title("<browser_instruction># Claude in Chrome\n\nUse the tools.</browser_instruction>Fix the sidebar under the app bar").as_deref(),
1139+ Some("Fix the sidebar under the app bar")
1140+ );
11331141 for prompt in ["/clear", "/compact keep the plan", "/g1t:status 4", "<command-name>/clear</command-name>", "<local-command-stdout lang=\"en\">ok</local-command-stdout>", "```\ncode only\n```", "?", ""] {
11341142 assert_eq!(intent_title(prompt), None, "{prompt}");
11351143 }

Comments

Sign in to comment.