Skip to main content

agent_profile/adapter/
continue.rs

1// Copyright (c) 2026 Costas Kirgoussios
2//
3// This software is licensed under the PolyForm Noncommercial License 1.0.0.
4// You may obtain a copy of the License at:
5// https://polyformproject.org/licenses/noncommercial/1.0.0/
6
7//! Continue CLI: `--config <profile config.yaml>` (SP4 design §8, table row; §9 outcome 5 — the flag is
8//! real and accepted, but the measured delta at the watched locations was empty).
9
10use std::path::PathBuf;
11
12use super::{
13    Adapter, AdapterEvidence, AdapterMetadata, Capability, CapabilityClaim, CapabilityState,
14    ConflictOption, Mechanism, PathKind, PlanContext, PlannedLaunch, SupportLevel, profile_dir,
15};
16use crate::config::AppRoot;
17use crate::error::Result;
18use crate::name::ProfileName;
19
20const FLAG: &str = "--config";
21const FILE_NAME: &str = "config.yaml";
22
23/// Every spelling of the option Continue launches with; `--config` first, so it is the one reported.
24/// Only this spelling was measured — the probe's candidate sweep probed file contents, not flag
25/// spellings, so Q6 stays unanswered and no abbreviation may ride along unmeasured (tracked debt).
26const CONFIG_OPTION: ConflictOption = ConflictOption { long: &[FLAG], short: None };
27
28/// A zero-byte `config.yaml`: the smallest content the agent accepts (candidate sweep: `""`, `# nothing`
29/// and `{}` all exited 0) that sets no option, so it is what ships per §8.4 — a config-file adapter
30/// needs the file to exist before `cn` reads it.
31pub const INITIAL_CONFIG: &[u8] = b"";
32
33static METADATA: AdapterMetadata = AdapterMetadata {
34    id: "continue",
35    executable: "cn",
36    mechanism: Mechanism::FlagFile(CONFIG_OPTION),
37    support: SupportLevel::Experimental,
38    evidence: AdapterEvidence {
39        mechanism_id: "continue-config-file-v1",
40        verified_at: "2026-10-17",
41        upstream_version: "1.5.47",
42        source_url: "measured",
43        notes: "measured in a sandbox at 1.5.47: --help lists `--config <path>  Configuration file for \
44                the assistant` and a launch with it exits 0; the delta at both watched locations was \
45                `(no change: nothing was created, moved or removed at the watched locations)` — the agent \
46                writes nothing before it needs the network (§9 outcome 5); the candidate sweep accepted \
47                the empty file, `# nothing` and {} (all exit 0), and the strings scan found CONTINUE_API_KEY, \
48                ANTHROPIC_API_KEY and OPENAI_API_KEY documented",
49    },
50    capabilities: &[
51        CapabilityClaim {
52            capability: Capability::ConfigIsolation,
53            state: CapabilityState::Unknown,
54            basis: "unmeasured: the probe's delta at both watched locations — the target and the default \
55                    `/home/agent/.continue` — was `(no change: nothing was created, moved or removed at the \
56                    watched locations)`, so whether the agent's own config writes would follow a configured \
57                    file was not observable while the agent writes nothing before it needs the network \
58                    (§9 outcome 5)",
59        },
60        CapabilityClaim {
61            capability: Capability::CredentialIsolation,
62            state: CapabilityState::Unknown,
63            basis: "unmeasured: no login/auth step ran and no credential-bearing file surfaced anywhere in \
64                    the delta, so where stored credentials live was not established; the documented bypass \
65                    variables are `CONTINUE_API_KEY`, `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` \
66                    (strings: `documented CONTINUE_API_KEY: present`)",
67        },
68        CapabilityClaim {
69            capability: Capability::StateIsolation,
70            state: CapabilityState::Unknown,
71            basis: "unmeasured: the agent's only pre-network write was `.continue/logs/cn.log`, present at \
72                    the default location at baseline, and the measured delta recorded \
73                    `(no change: nothing was created, moved or removed at the watched locations)`, so no \
74                    state write was observable to place (§9 outcome 5)",
75        },
76    ],
77    env: &[],
78    // Empty on purpose. `--config` is the mechanism's own option, and `check_conflicts` scans that
79    // alongside this list, so naming it here again refused nothing extra. No abbreviated spelling was
80    // measured (Q6 unanswered), so there is no list behind it either.
81    conflicts: &[],
82    credential_env: &["CONTINUE_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
83};
84
85/// The Continue adapter.
86#[derive(Debug, Clone, Copy)]
87pub struct Continue;
88
89impl Adapter for Continue {
90    fn metadata(&self) -> &'static AdapterMetadata {
91        &METADATA
92    }
93
94    fn paths(&self, root: &AppRoot, profile: &ProfileName) -> Vec<(PathBuf, PathKind)> {
95        let dir = profile_dir(root, profile, METADATA.id);
96        let file = dir.join(FILE_NAME);
97        vec![(dir, PathKind::Dir), (file, PathKind::File { contents: INITIAL_CONFIG })]
98    }
99
100    fn plan(&self, ctx: &PlanContext<'_>) -> Result<PlannedLaunch> {
101        METADATA.mechanism.plan(self, ctx)
102    }
103}