cc-transcript 10.4.0__tar.gz → 10.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/PKG-INFO +1 -1
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/activity.py +7 -2
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/filterspec.py +5 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/models.py +4 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/parser.py +2 -1
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/pyproject.toml +1 -1
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/activity.rs +25 -4
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/event.rs +3 -1
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/protocol.rs +37 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/types.rs +5 -1
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/Cargo.lock +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/Cargo.toml +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/LICENSE +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/README.md +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/__init__.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/__main__.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/_parser_rs.pyi +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/activity_probe.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/backend.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/builders.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/cli.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/command.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/context.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/corrections.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/corrections_cli.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/cost.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/decisions.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/discovery.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/disktruth.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/evidence.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/extract/__init__.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/extract/correct.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/facts.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/ids.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/judge/__init__.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/judge/llm.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/judge/similar.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/judge/verdicts.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/__init__.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/candidates.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/confidence.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/engine.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/filterspec.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/formats.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/signals.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/sourcekind.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/spec.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/mining/store.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/notifications.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/py.typed +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/query.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/render.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/rust.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/sentiment/__init__.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/sentiment/buckets.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/sentiment/engine.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/sentiment/lexicon.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/sentiment/scorespec.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/store.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/cc_transcript/tools.py +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/Cargo.toml +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/build.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/data/afinn-en-165.tsv +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/data/domain_overrides.tsv +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/command.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/filter.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/lexicon.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/lib.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/mining.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/model.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/parse.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/python.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/score.rs +0 -0
- {cc_transcript-10.4.0 → cc_transcript-10.5.0}/rust/src/value.rs +0 -0
|
@@ -40,10 +40,15 @@ def native_user_classifier(event: UserEvent) -> bool:
|
|
|
40
40
|
"""Whether a user event is a real prompt under native Claude Code semantics.
|
|
41
41
|
|
|
42
42
|
A prompt is non-meta, non-sidechain, not a compact summary, not an
|
|
43
|
-
interruption marker,
|
|
43
|
+
interruption marker, not an agent-injected relay banner, and not
|
|
44
|
+
tool-result-only — it must carry real text.
|
|
44
45
|
"""
|
|
45
46
|
return not (
|
|
46
|
-
event.meta.is_meta
|
|
47
|
+
event.meta.is_meta
|
|
48
|
+
or event.meta.is_sidechain
|
|
49
|
+
or event.meta.is_compact_summary
|
|
50
|
+
or event.interrupted
|
|
51
|
+
or event.is_agent_injected
|
|
47
52
|
) and bool(event.text.strip())
|
|
48
53
|
|
|
49
54
|
|
|
@@ -419,6 +419,10 @@ def is_bare_interrupt_marker(text: str) -> bool:
|
|
|
419
419
|
return (marker := interrupt_marker(text)) is not None and not text.strip()[len(marker.strip()) :].strip()
|
|
420
420
|
|
|
421
421
|
|
|
422
|
+
def is_agent_injection(text: str) -> bool:
|
|
423
|
+
return AGENT_INJECTION_RE.search(text) is not None
|
|
424
|
+
|
|
425
|
+
|
|
422
426
|
def normalize_bare(text: str, strip_trailing: str = TRAILING_PUNCT) -> str:
|
|
423
427
|
return text.strip().rstrip(strip_trailing).strip().lower()
|
|
424
428
|
|
|
@@ -551,5 +555,6 @@ def predicate_to_dict(predicate: Predicate) -> dict[str, Any]:
|
|
|
551
555
|
|
|
552
556
|
|
|
553
557
|
STRUCTURAL_NOISE_RE = compile_groups(STRUCTURAL_NOISE_GROUPS, True)
|
|
558
|
+
AGENT_INJECTION_RE = compile_groups(AGENT_INJECTION_GROUPS, True)
|
|
554
559
|
INTERRUPT_MARKER_RE = compile_groups(INTERRUPT_MARKER_GROUPS, True)
|
|
555
560
|
JUNK_USER_MESSAGE_RE = compile_groups(SENTIMENT_JUNK_GROUPS, True)
|
|
@@ -160,12 +160,16 @@ class UserEvent:
|
|
|
160
160
|
text: The joined text of the turn.
|
|
161
161
|
blocks: The parsed content blocks, including tool results.
|
|
162
162
|
interrupted: Whether the turn is a user interruption.
|
|
163
|
+
is_agent_injected: Whether the turn is an agent-injected relay banner —
|
|
164
|
+
a teammate-message digest, scheduled-task banner, or foreign-agent
|
|
165
|
+
header — rather than an authored prompt.
|
|
163
166
|
"""
|
|
164
167
|
|
|
165
168
|
meta: EntryMeta
|
|
166
169
|
text: str
|
|
167
170
|
blocks: tuple[ContentBlock, ...]
|
|
168
171
|
interrupted: bool
|
|
172
|
+
is_agent_injected: bool = False
|
|
169
173
|
|
|
170
174
|
|
|
171
175
|
@dataclass(frozen=True, slots=True)
|
|
@@ -11,7 +11,7 @@ import anyio.to_thread
|
|
|
11
11
|
import orjson
|
|
12
12
|
|
|
13
13
|
from cc_transcript.backend import Backend, ParsedTranscript
|
|
14
|
-
from cc_transcript.filterspec import apply_spec, interrupt_marker
|
|
14
|
+
from cc_transcript.filterspec import apply_spec, interrupt_marker, is_agent_injection
|
|
15
15
|
from cc_transcript.models import (
|
|
16
16
|
AssistantEvent,
|
|
17
17
|
CacheCreation,
|
|
@@ -158,6 +158,7 @@ def parse_event(data: Mapping[str, Any]) -> TranscriptEvent | None:
|
|
|
158
158
|
text=text,
|
|
159
159
|
blocks=blocks,
|
|
160
160
|
interrupted=interrupt_marker(text) is not None,
|
|
161
|
+
is_agent_injected=is_agent_injection(text),
|
|
161
162
|
)
|
|
162
163
|
case "assistant":
|
|
163
164
|
text, blocks = parse_assistant_blocks(data["message"]["content"])
|
|
@@ -73,11 +73,16 @@ pub struct SessionActivity {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/// Whether a user entry opens a turn (activity.py ``native_user_classifier``):
|
|
76
|
-
/// a real prompt — non-meta, non-sidechain, not an interruption marker,
|
|
77
|
-
/// non-blank text. Compact-summary exclusion
|
|
78
|
-
/// consumes this probe since the 10.2.0
|
|
76
|
+
/// a real prompt — non-meta, non-sidechain, not an interruption marker, not an
|
|
77
|
+
/// agent-injected relay banner, with non-blank text. Compact-summary exclusion
|
|
78
|
+
/// matches captain-hook, which consumes this probe since the 10.2.0
|
|
79
|
+
/// shared-classifier fix.
|
|
79
80
|
fn opens_turn(user: &UserEntry) -> bool {
|
|
80
|
-
!(user.meta.is_meta
|
|
81
|
+
!(user.meta.is_meta
|
|
82
|
+
|| user.meta.is_sidechain
|
|
83
|
+
|| user.meta.is_compact_summary
|
|
84
|
+
|| user.interrupted()
|
|
85
|
+
|| user.is_agent_injected())
|
|
81
86
|
&& !user.content.text().trim().is_empty()
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -510,6 +515,22 @@ mod tests {
|
|
|
510
515
|
assert_eq!(activity.pending[0].kind, PendingKind::Background);
|
|
511
516
|
}
|
|
512
517
|
|
|
518
|
+
#[test]
|
|
519
|
+
fn agent_injected_banner_does_not_open_turn() {
|
|
520
|
+
let entries = vec![
|
|
521
|
+
user("build it"),
|
|
522
|
+
tool_use("Bash", "b1", r#"{"command":"make","run_in_background":true}"#),
|
|
523
|
+
tool_result("b1"),
|
|
524
|
+
user("<teammate-message from='mate'>ping</teammate-message>"),
|
|
525
|
+
];
|
|
526
|
+
let activity = activity(&entries);
|
|
527
|
+
assert!(
|
|
528
|
+
activity.is_waiting,
|
|
529
|
+
"an agent-injected relay banner must not open a turn, so the background Bash stays current"
|
|
530
|
+
);
|
|
531
|
+
assert_eq!(activity.pending[0].kind, PendingKind::Background);
|
|
532
|
+
}
|
|
533
|
+
|
|
513
534
|
#[test]
|
|
514
535
|
fn unrelated_completion_marker_keeps_waiting() {
|
|
515
536
|
let entries = vec![
|
|
@@ -12,7 +12,7 @@ use crate::model::{
|
|
|
12
12
|
USER_EVENT_CLS,
|
|
13
13
|
};
|
|
14
14
|
use crate::parse::ParseError;
|
|
15
|
-
use crate::protocol::interrupt_marker;
|
|
15
|
+
use crate::protocol::{interrupt_marker, is_agent_injection};
|
|
16
16
|
use crate::types::{
|
|
17
17
|
joined_text, ContentBlock, Entry, EntryMeta, InitInfo, ModelUsage, PrintBody, PrintMessage,
|
|
18
18
|
PrintResult, Usage, UserContent,
|
|
@@ -155,11 +155,13 @@ pub fn build_event<'py>(py: Python<'py>, entry: &Entry) -> PyResult<Bound<'py, P
|
|
|
155
155
|
Entry::User(user) => {
|
|
156
156
|
let text = user.content.text();
|
|
157
157
|
let interrupted = interrupt_marker(&text).is_some();
|
|
158
|
+
let is_agent_injected = is_agent_injection(&text);
|
|
158
159
|
models_type(py, &USER_EVENT_CLS, "UserEvent")?.call1((
|
|
159
160
|
build_meta(py, &user.meta)?,
|
|
160
161
|
text,
|
|
161
162
|
build_user_blocks(py, &user.content)?,
|
|
162
163
|
interrupted,
|
|
164
|
+
is_agent_injected,
|
|
163
165
|
))
|
|
164
166
|
}
|
|
165
167
|
Entry::Assistant(assistant) => {
|
|
@@ -12,6 +12,12 @@ pub const ANSWERED_PREFIX: &str = "Your questions have been answered: ";
|
|
|
12
12
|
pub const ANSWERED_TRAILER: &str = ". You can now continue with these answers in mind.";
|
|
13
13
|
const INTERRUPT_MARKER_PATTERN: &str = r"^\s*\[Request interrupted by user";
|
|
14
14
|
|
|
15
|
+
// Agent-injected relay banners (filterspec.py AGENT_INJECTION_GROUPS): teammate-message
|
|
16
|
+
// digests, scheduled-task banners, and foreign-agent headers. The alternation mirrors
|
|
17
|
+
// compile_groups(AGENT_INJECTION_GROUPS): `(?:p1)|(?:p2)|(?:p3)`.
|
|
18
|
+
const AGENT_INJECTION_PATTERN: &str =
|
|
19
|
+
r"(?:<(?:teammate-message|scheduled-task)\b)|(?:^# Augment Agent\b)|(?:^\s*\[Role Reminder\b)";
|
|
20
|
+
|
|
15
21
|
/// The one interrupt-marker regex (filterspec.py INTERRUPT_MARKER_RE): the pattern
|
|
16
22
|
/// compiled case-insensitively, anchored at the start of the haystack with leading
|
|
17
23
|
/// whitespace tolerated. Shared with mining's structural fallback so both uses
|
|
@@ -20,6 +26,19 @@ pub static INTERRUPT_MARKER_RE: Lazy<Regex> = Lazy::new(|| {
|
|
|
20
26
|
Regex::new(&format!("(?i){INTERRUPT_MARKER_PATTERN}")).expect("interrupt regex")
|
|
21
27
|
});
|
|
22
28
|
|
|
29
|
+
/// The agent-injection regex (filterspec.py AGENT_INJECTION_RE): the group
|
|
30
|
+
/// alternation compiled case-insensitively, matched with search semantics
|
|
31
|
+
/// (filterspec.py is_agent_injection uses .search()).
|
|
32
|
+
pub static AGENT_INJECTION_RE: Lazy<Regex> = Lazy::new(|| {
|
|
33
|
+
Regex::new(&format!("(?i){AGENT_INJECTION_PATTERN}")).expect("agent injection regex")
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/// is_agent_injection (filterspec.py is_agent_injection): whether ``text`` is an
|
|
37
|
+
/// agent-injected relay banner rather than an authored prompt.
|
|
38
|
+
pub fn is_agent_injection(text: &str) -> bool {
|
|
39
|
+
AGENT_INJECTION_RE.is_match(text)
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
/// embedded_user_text (filterspec.py embedded_user_text): the verbatim instruction
|
|
24
43
|
/// wrapped between the USER_SAID markers, or None.
|
|
25
44
|
pub fn embedded_user_text(content: &str) -> Option<String> {
|
|
@@ -91,4 +110,22 @@ mod tests {
|
|
|
91
110
|
assert!(is_bare_interrupt_marker("[Request interrupted by user]"));
|
|
92
111
|
assert!(!is_bare_interrupt_marker("[Request interrupted by user] no do it differently"));
|
|
93
112
|
}
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
fn agent_injection_matches_relay_banners() {
|
|
116
|
+
assert!(is_agent_injection("<teammate-message from='r'>rebase</teammate-message>"));
|
|
117
|
+
assert!(is_agent_injection("<scheduled-task id='7'>run the suite</scheduled-task>"));
|
|
118
|
+
assert!(is_agent_injection("[Role Reminder: You are a Coordinator."));
|
|
119
|
+
assert!(is_agent_injection("# Augment Agent\nyou have these tools"));
|
|
120
|
+
// search() is unanchored: a mid-text teammate tag still matches.
|
|
121
|
+
assert!(is_agent_injection("as noted in the <teammate-message> above"));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[test]
|
|
125
|
+
fn agent_injection_rejects_prose_and_head_anchored_mid_text() {
|
|
126
|
+
assert!(!is_agent_injection("remind me what the teammate coordinator does"));
|
|
127
|
+
// the head-anchored role-reminder group does not match mid-text.
|
|
128
|
+
assert!(!is_agent_injection("we discussed the [Role Reminder] banner mid-sentence"));
|
|
129
|
+
assert!(!is_agent_injection(""));
|
|
130
|
+
}
|
|
94
131
|
}
|
|
@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
|
|
|
3
3
|
use chrono::{DateTime, FixedOffset};
|
|
4
4
|
use sonic_rs::Value;
|
|
5
5
|
|
|
6
|
-
use crate::protocol::interrupt_marker;
|
|
6
|
+
use crate::protocol::{interrupt_marker, is_agent_injection};
|
|
7
7
|
|
|
8
8
|
/// Envelope metadata shared by the conversational entry kinds (user, assistant,
|
|
9
9
|
/// system). Mode and Other entries carry no envelope on disk.
|
|
@@ -116,6 +116,10 @@ impl UserEntry {
|
|
|
116
116
|
pub fn interrupted(&self) -> bool {
|
|
117
117
|
interrupt_marker(&self.content.text()).is_some()
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
pub fn is_agent_injected(&self) -> bool {
|
|
121
|
+
is_agent_injection(&self.content.text())
|
|
122
|
+
}
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
#[derive(Debug)]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|