Skip to content
This repository was archived by the owner on Jul 30, 2026. It is now read-only.
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
6 changes: 2 additions & 4 deletions .github/workflows/test_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:

coverage:
name: Code Coverage
if: github.event_name != 'pull_request' && github.ref_name == github.event.repository.default_branch
if: github.event_name == 'pull_request' || github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest

permissions:
Expand Down Expand Up @@ -101,11 +101,9 @@ jobs:
run: cargo test --locked --workspace --all-features

- name: Install cargo-tarpaulin
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
uses: taiki-e/install-action@51b9b111bd6914e2e43286340cbdbf490ef816a3 # cargo-tarpaulin

- name: Generate coverage report
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
run: |
cargo tarpaulin \
--workspace \
Expand All @@ -118,7 +116,7 @@ jobs:

- name: Upload coverage artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
if: "!cancelled() && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')"
if: "!cancelled()"
with:
name: coverage-report
path: ./coverage/
Expand Down
2 changes: 1 addition & 1 deletion crates/tokscale-cli/src/tui/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use super::data::{

/// Cache staleness threshold: 5 minutes (matches TS implementation)
const CACHE_STALE_THRESHOLD_MS: u64 = 5 * 60 * 1000;
const CACHE_SCHEMA_VERSION: u32 = 28;
const CACHE_SCHEMA_VERSION: u32 = 30;

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down
3 changes: 1 addition & 2 deletions crates/tokscale-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3627,8 +3627,7 @@ fn light_report_surfaces_malformed_display_config_without_panicking() {
.stderr(
predicate::str::contains("failed to parse TOML config")
.and(predicate::str::contains(config_path.display().to_string()))
.and(predicate::str::contains("panicked").not())
.and(predicate::str::contains("backtrace").not()),
.and(predicate::str::contains("panicked").not()),
);
}

Expand Down
4 changes: 2 additions & 2 deletions crates/tokscale-core/src/adapters/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::clients::ClientId;
use crate::message_cache::{ParserId, ParserVersion};
use crate::{cc_mirror, sessions};

const CLAUDE_WORKFLOW_REVISION: u32 = MODEL_ID_CANONICALIZATION_REVISION + 1;
const CLAUDE_WORKFLOW_AND_AGENT_IDENTITY_REVISION: u32 = MODEL_ID_CANONICALIZATION_REVISION + 2;

pub(crate) struct ClaudeAdapter;

Expand Down Expand Up @@ -69,7 +69,7 @@ impl LocalSourceAdapter for ClaudeAdapter {
.map(|unit| {
unit.with_parser_version(ParserVersion::new(
ParserId::Claude,
CLAUDE_WORKFLOW_REVISION,
CLAUDE_WORKFLOW_AND_AGENT_IDENTITY_REVISION,
))
})
.collect();
Expand Down
13 changes: 5 additions & 8 deletions crates/tokscale-core/src/adapters/omp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ pub(crate) struct OmpAdapter;

pub(crate) static OMP_ADAPTER: OmpAdapter = OmpAdapter;

// Earlier OMP revisions were emitted before malformed inclusive-reasoning
// breakdowns were clamped to their authoritative output bucket.
const OMP_USAGE_AND_SWARM_REVISION: u32 = crate::adapters::MODEL_ID_CANONICALIZATION_REVISION + 4;
// Earlier OMP revisions emitted per-agent swarm labels instead of the shared
// reporting identity used by the Agents tab.
const OMP_USAGE_AND_SWARM_REVISION: u32 = crate::adapters::MODEL_ID_CANONICALIZATION_REVISION + 5;

impl LocalSourceAdapter for OmpAdapter {
fn client(&self) -> ClientId {
Expand Down Expand Up @@ -393,7 +393,7 @@ mod tests {
}

#[test]
fn omp_adapter_recovers_swarm_agent_from_canonical_extra_path() {
fn omp_adapter_groups_canonical_swarm_agents_under_shared_identity() {
let home = tempfile::TempDir::new().unwrap();
let extra_root = home.path().join("omp-archive");
let artifact_path = extra_root.join(
Expand All @@ -418,10 +418,7 @@ mod tests {
let messages = fold_with_omp_adapter(units, &mut cache);

assert_eq!(messages.len(), 1);
assert_eq!(
messages[0].agent.as_deref(),
Some("OMP Swarm architecture-reviewer")
);
assert_eq!(messages[0].agent.as_deref(), Some("OMP Swarm"));
assert_eq!(
messages[0].agent_instance.as_deref(),
Some("swarm-docs-factcheck-architecture-reviewer-12")
Expand Down
20 changes: 10 additions & 10 deletions crates/tokscale-core/src/sessions/claudecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ fn resolve_subagent_name(
));
}
return Ok(normalize_claude_agent_label(agent_type)
.unwrap_or_else(|| super::normalize_agent_name(agent_type)));
.unwrap_or_else(|| "Claude Subagent".to_string()));
}

// Tier 2: parent session tool_use inference
Expand All @@ -194,7 +194,7 @@ fn resolve_subagent_name(
));
}
return Ok(normalize_claude_agent_label(subagent_type)
.unwrap_or_else(|| super::normalize_agent_name(subagent_type)));
.unwrap_or_else(|| "Claude Subagent".to_string()));
}
}
}
Expand Down Expand Up @@ -3164,7 +3164,7 @@ mod tests {
}

#[test]
fn test_sidechain_temporary_meta_agent_type_preserves_custom_identity() {
fn test_sidechain_temporary_meta_agent_type_uses_generic_identity() {
let jsonl = r#"{"type":"user","isSidechain":true,"sessionId":"parent-temp-001","agentId":"temp1","timestamp":"2024-12-01T10:00:00.000Z","message":{"content":"Scan auth"}}
{"type":"assistant","isSidechain":true,"sessionId":"parent-temp-001","agentId":"temp1","timestamp":"2024-12-01T10:00:01.000Z","requestId":"req_temp","message":{"id":"msg_temp","model":"claude-sonnet-4.6","usage":{"input_tokens":200,"output_tokens":80}}}"#;
let meta = r#"{"agentType":"auth-scanner"}"#;
Expand All @@ -3179,7 +3179,7 @@ mod tests {
let messages = parse_claude_file(&path).unwrap();

assert_eq!(messages.len(), 1);
assert_eq!(messages[0].agent.as_deref(), Some("Auth Scanner"));
assert_eq!(messages[0].agent.as_deref(), Some("Claude Subagent"));
}

#[test]
Expand Down Expand Up @@ -3298,8 +3298,8 @@ mod tests {
assert_eq!(total_cache_write, 150, "cache_write: 100 + 50");

// Both messages should have the same agent
assert_eq!(messages[0].agent.as_deref(), Some("Code Reviewer"));
assert_eq!(messages[1].agent.as_deref(), Some("Code Reviewer"));
assert_eq!(messages[0].agent.as_deref(), Some("Claude Subagent"));
assert_eq!(messages[1].agent.as_deref(), Some("Claude Subagent"));
}

#[test]
Expand Down Expand Up @@ -3362,8 +3362,8 @@ mod tests {
);
assert_eq!(
messages[0].agent,
Some("Architect".into()),
"Deduped message should retain the custom agent identity"
Some("Claude Subagent".into()),
"Deduped message should retain the generic agent identity"
);
assert_eq!(messages[0].session_id.as_ref(), "parent-dedup");
}
Expand Down Expand Up @@ -3437,8 +3437,8 @@ mod tests {
assert_eq!(messages.len(), 1);
assert_eq!(
messages[0].agent,
Some("Document Specialist".into()),
"Tier 2 should retain custom parent subagent identity"
Some("Claude Subagent".into()),
"Tier 2 should collapse custom parent subagent identities"
);
assert_eq!(messages[0].session_id.as_ref(), parent_session_id);
}
Expand Down
27 changes: 15 additions & 12 deletions crates/tokscale-core/src/sessions/pi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ fn omp_swarm_agent_label_from_path(path: &Path) -> SessionParseResult<Option<Str
));
}

Ok(Some(format!("OMP Swarm {agent_name}")))
Ok(Some("OMP Swarm".to_string()))
}

fn omp_parent_session_path(path: &Path) -> SessionParseResult<Option<PathBuf>> {
Expand Down Expand Up @@ -1111,29 +1111,32 @@ mod tests {
}

#[test]
fn test_parse_omp_swarm_artifact_recovers_agent_identity() {
fn test_parse_omp_swarm_artifact_uses_shared_agent_identity() {
let dir = TempDir::new().unwrap();
let context = dir.path().join(".swarm_docs-factcheck").join("context");
std::fs::create_dir_all(&context).unwrap();
let path = context.join("swarm-docs-factcheck-architecture-reviewer-2.jsonl");
std::fs::write(
&path,
r#"{"type":"session","id":"swarm-session","timestamp":"2026-01-01T00:00:00.000Z","cwd":"/tmp"}
{"type":"message","id":"msg_001","parentId":null,"timestamp":"2026-01-01T00:00:01.000Z","message":{"role":"assistant","model":"gpt-5.5","provider":"openai","usage":{"input":20,"output":10,"cacheRead":0,"cacheWrite":0,"totalTokens":30}}}"#,
)
.unwrap();
let second_path = context.join("swarm-docs-factcheck-implementation-reviewer-3.jsonl");
let content = r#"{"type":"session","id":"swarm-session","timestamp":"2026-01-01T00:00:00.000Z","cwd":"/tmp"}
{"type":"message","id":"msg_001","parentId":null,"timestamp":"2026-01-01T00:00:01.000Z","message":{"role":"assistant","model":"gpt-5.5","provider":"openai","usage":{"input":20,"output":10,"cacheRead":0,"cacheWrite":0,"totalTokens":30}}}"#;
std::fs::write(&path, content).unwrap();
std::fs::write(&second_path, content).unwrap();

let messages = parse_omp_file(&path).unwrap();
let second_messages = parse_omp_file(&second_path).unwrap();

assert_eq!(messages.len(), 1);
assert_eq!(
messages[0].agent.as_deref(),
Some("OMP Swarm architecture-reviewer")
);
assert_eq!(second_messages.len(), 1);
assert_eq!(messages[0].agent.as_deref(), Some("OMP Swarm"));
assert_eq!(second_messages[0].agent.as_deref(), Some("OMP Swarm"));
assert_eq!(
messages[0].agent_instance.as_deref(),
Some("swarm-docs-factcheck-architecture-reviewer-2")
);
assert_eq!(
second_messages[0].agent_instance.as_deref(),
Some("swarm-docs-factcheck-implementation-reviewer-3")
);
}

#[test]
Expand Down
9 changes: 4 additions & 5 deletions docs/adr/0006-agent-identity-for-agents-tab.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ Group `Agents` rows by stable agent identity only.
a grouping identity.
- Claude preserves known stable subagent types and collapses unknown temporary
sidechain names to `Claude Subagent`.
- OMP recovers task agent roles from parent `task` calls and official swarm
artifact paths. A canonical
`.swarm_<swarm>/context/swarm-<swarm>-<agent>-<iteration>.jsonl` path supplies
the stable swarm agent label, while the full artifact stem remains the
instance identifier.
- OMP recovers task agent roles from parent `task` calls. Canonical
`.swarm_<swarm>/context/swarm-<swarm>-<agent>-<iteration>.jsonl` artifacts
share the stable `OMP Swarm` reporting identity, while the full artifact stem
remains the instance identifier.
- Kimi uses explicit `config.update.profileName` values from the known profile
set only; filesystem segments such as `main` and `agent-N` are not fallbacks.
- Messages without a recognized stable agent identity should not create an
Expand Down
Loading