Skip to content
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
11 changes: 11 additions & 0 deletions rust/crates/sift_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
`report_template_url` link to the template, and `list_report_templates` adds a
`url` to each template. Agents no longer link a new template to a report's
page.
- The artifact tools now return the artifact's Sift web link. `list_artifacts`
rows carry `url`, and `list_artifact_versions`, `download_artifact`,
`create_artifact`, `update_artifact`, `archive_artifact`, and
`unarchive_artifact` return `artifact_url`. `download_artifact` links the
requested version when `artifact_version_id` is set. Agents link the artifact
page instead of the Sift home page.
- A name with a stray `[` or `]`, such as `results v2].csv`, no longer breaks
the Markdown link a tool tells the agent to show.
- The Sift skill now says to present a URL a tool returns as a Markdown link
named for the item, the same as the tools say. It used to ask for plain
text, so agents got conflicting instructions.

## [v0.7.3] - October 7, 2026

Expand Down
7 changes: 4 additions & 3 deletions rust/crates/sift_cli/assets/skills/sift/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,10 @@ tool.
- **Stop on empty list results.** If any `list_*` tool returns no items, tell
the user that nothing matched and ask how to proceed. Do not make subsequent
tool calls using an empty or placeholder name or id from the missing result.
- **Surface URLs as plain text, in full.** The link from `explore_url` and the
`View in Sift:` line from `sift-cli import` are deliverables. Never invent a
URL that a tool did not return.
- **Present a returned URL as a Markdown link.** Use the item's name as the
text, or what an Explore view shows, never a bare id or the word "link". The
link from `explore_url` and the `View in Sift:` line from `sift-cli import`
are deliverables. Never invent a URL that a tool did not return.
- **Confirm every write before you run it.** Show the user the proposed change
and its target, then wait for approval.
- **Write tools are off by default.** Read-only is the default access mode.
Expand Down
4 changes: 2 additions & 2 deletions rust/crates/sift_cli/assets/skills/sift/references/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ per session. The rest apply to each subcommand invocation.
accepts no `--wait`, `--preview`, or `--run`.
7. **Surface the Explore link from import output.** Each profile must set
`app_uri`. `sift-cli import` prints a `View in Sift: <URL>` tip when this
value is usable. Surface the URL as plain text, in full. Do not wrap it in a
markdown link. An incomplete old profile does not block the import. The CLI
value is usable. Present the URL as a Markdown link whose text names the
imported data. An incomplete old profile does not block the import. The CLI
prints a config note instead. Relay that note. For an unknown domain, ask
the user to copy the origin from their Sift web app. Do not assume a
top-level domain. Do not invent a URL.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Sift Explore links

Build the link with `explore_url`, then surface the URL to the user as plain
text, in full.
Build the link with `explore_url`, then present it to the user as a Markdown
link whose text names what the view shows.

Send one source type. Pass `run_ids` when the request names a run and
`asset_ids` otherwise. Use IDs returned by `list_runs` and `list_assets`; the
Expand Down
15 changes: 15 additions & 0 deletions rust/crates/sift_mcp/src/service/url/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ impl UrlService {
Ok(format!("{host}/run/{}", encode_value(run_id)))
}

/// The artifact page follows the latest version unless `artifact_version_id`
/// pins it to one.
pub fn build_artifact_url(
&self,
artifact_id: &str,
artifact_version_id: Option<&str>,
) -> Result<String, ErrorData> {
let host = self.app_host()?;
let url = format!("{host}/artifacts/{}", encode_value(artifact_id));
Ok(match artifact_version_id {
Some(version_id) => format!("{url}?artifactVersionId={}", encode_value(version_id)),
None => url,
})
}

fn app_host(&self) -> Result<&str, ErrorData> {
let host = self.app_uri.trim().trim_end_matches('/');
(!host.is_empty()).then_some(host).ok_or_else(|| {
Expand Down
13 changes: 13 additions & 0 deletions rust/crates/sift_mcp/src/service/url/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,16 @@ fn annotation_asset_run_urls_use_singular_path_segments() {
"https://app.siftstack.com/run/run-1"
);
}

#[test]
fn artifact_url_pins_a_version_only_when_given_one() {
let svc = service();
assert_eq!(
svc.build_artifact_url("art-1", None).unwrap(),
"https://app.siftstack.com/artifacts/art-1"
);
assert_eq!(
svc.build_artifact_url("art-1", Some("ver 2")).unwrap(),
"https://app.siftstack.com/artifacts/art-1?artifactVersionId=ver%202"
);
}
Loading
Loading