@team-agent/installer 0.5.77 → 0.5.80
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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +161 -29
- package/crates/team-agent/src/cli/diagnose.rs +333 -68
- package/crates/team-agent/src/cli/emit.rs +24 -8
- package/crates/team-agent/src/cli/mod.rs +543 -98
- package/crates/team-agent/src/cli/models.rs +393 -32
- package/crates/team-agent/src/cli/named_address.rs +19 -5
- package/crates/team-agent/src/cli/profile.rs +35 -4
- package/crates/team-agent/src/cli/spec.rs +7 -2
- package/crates/team-agent/src/cli/status_port/compact.rs +12 -8
- package/crates/team-agent/src/cli/status_port/runtime.rs +3 -1
- package/crates/team-agent/src/cli/status_port/snapshot.rs +15 -12
- package/crates/team-agent/src/cli/status_port/store.rs +1 -1
- package/crates/team-agent/src/cli/status_port/tests.rs +37 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +62 -8
- package/crates/team-agent/src/cli/tests/named_address.rs +70 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +81 -6
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +122 -2
- package/crates/team-agent/src/leader/lease.rs +492 -33
- package/crates/team-agent/src/leader/owner_bind.rs +40 -12
- package/crates/team-agent/src/leader/rediscover/tests.rs +65 -0
- package/crates/team-agent/src/leader/rediscover.rs +36 -6
- package/crates/team-agent/src/leader/start.rs +197 -88
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +15 -9
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +28 -227
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -18
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +94 -227
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +59 -10
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +86 -23
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1449 -282
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +349 -4
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +11 -11
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +35 -3
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +16 -0
- package/crates/team-agent/src/lifecycle/launch.rs +30 -7
- package/crates/team-agent/src/lifecycle/profile_launch.rs +37 -3
- package/crates/team-agent/src/lifecycle/restart/agent.rs +112 -100
- package/crates/team-agent/src/lifecycle/restart/common.rs +82 -52
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +996 -66
- package/crates/team-agent/src/lifecycle/restart.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +15 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +5 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +11 -13
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +119 -4
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +22 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +265 -198
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +14 -30
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_spaced_ids.stdout.txt +9 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +2 -5
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +58 -35
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +9 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -168
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +145 -212
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +16 -2
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +54 -9
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +71 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +78 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +51 -10
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +50 -7
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +137 -0
- package/crates/team-agent/src/lifecycle/types.rs +8 -0
- package/crates/team-agent/src/mcp_server/wire.rs +140 -11
- package/crates/team-agent/src/messaging/delivery.rs +221 -80
- package/crates/team-agent/src/messaging/leader_channel.rs +56 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +127 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +10 -2
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +2 -5
- package/crates/team-agent/src/provider/session_scan/common/tests.rs +204 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +24 -2
- package/crates/team-agent/src/state/abnormal_watch/tests/mod.rs +248 -0
- package/crates/team-agent/src/state/abnormal_watch.rs +144 -0
- package/crates/team-agent/src/state/mod.rs +1 -0
- package/crates/team-agent/src/state/persist.rs +102 -4
- package/crates/team-agent/src/state/projection.rs +7 -1
- package/crates/team-agent/src/state/repository.rs +29 -0
- package/crates/team-agent/src/test_support/models_timeout_fixture.rs +43 -21
- package/crates/team-agent/src/tmux_backend/tests.rs +149 -4
- package/crates/team-agent/src/tmux_backend.rs +239 -127
- package/crates/team-agent/src/transport.rs +78 -0
- package/crates/team-agent/src/transport_factory.rs +53 -0
- package/package.json +4 -5
- package/skills/team-agent/SKILL.md +45 -17
- package/skills/team-agent/command-coverage.json +4 -29
- package/npm/check-skill-links.mjs +0 -88
- package/skills/team-agent/references/team-agent-operator.md +0 -672
|
@@ -467,6 +467,21 @@ pub struct InjectReport {
|
|
|
467
467
|
/// pasted-prompt branch + (informational) for the appear-gate poll.
|
|
468
468
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
469
469
|
pub struct SubmitDiagnostics {
|
|
470
|
+
/// The concrete branch that produced the post-Enter consumption outcome.
|
|
471
|
+
/// Unknown is retained for paths that do not expose a branch reason.
|
|
472
|
+
pub consumption_reason: SubmitConsumptionReason,
|
|
473
|
+
/// Reserved for a pre-paste baseline; this path has no such capture and emits null.
|
|
474
|
+
pub before_busy_signal_kind: Option<BusySignalKind>,
|
|
475
|
+
/// Structured busy predicate category for the accepted/current capture.
|
|
476
|
+
pub busy_signal_kind: Option<BusySignalKind>,
|
|
477
|
+
/// Bottom-relative non-empty line index of the busy match.
|
|
478
|
+
pub busy_line_from_bottom: Option<u8>,
|
|
479
|
+
/// Whether this message marker is in the bottom 15 non-empty lines.
|
|
480
|
+
pub current_marker_in_bottom_15: Option<bool>,
|
|
481
|
+
/// Whether the tracked paste identity is still in the composer.
|
|
482
|
+
pub paste_identity_in_composer: Option<bool>,
|
|
483
|
+
/// Result of the structural consumption predicate on the current capture.
|
|
484
|
+
pub consumption_from_capture_result: Option<bool>,
|
|
470
485
|
/// Time spent in the appear-gate (poll for the pasted-content placeholder
|
|
471
486
|
/// before Enter). When `saw_pasted_prompt == false` this is the time we
|
|
472
487
|
/// spent polling before falling through to the E46 token path.
|
|
@@ -505,6 +520,63 @@ pub struct SubmitDiagnostics {
|
|
|
505
520
|
pub target_pane_query_matched: Option<bool>,
|
|
506
521
|
}
|
|
507
522
|
|
|
523
|
+
/// Structured category returned by the provider-busy predicate.
|
|
524
|
+
///
|
|
525
|
+
/// This is diagnostic metadata only; it does not change delivery semantics.
|
|
526
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
527
|
+
pub enum BusySignalKind {
|
|
528
|
+
Working,
|
|
529
|
+
Thinking,
|
|
530
|
+
Processing,
|
|
531
|
+
EscToInterrupt,
|
|
532
|
+
SpinnerGlyph,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
impl BusySignalKind {
|
|
536
|
+
pub fn as_str(self) -> &'static str {
|
|
537
|
+
match self {
|
|
538
|
+
Self::Working => "working",
|
|
539
|
+
Self::Thinking => "thinking",
|
|
540
|
+
Self::Processing => "processing",
|
|
541
|
+
Self::EscToInterrupt => "esc_to_interrupt",
|
|
542
|
+
Self::SpinnerGlyph => "spinner_glyph",
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// Structured reason for the post-Enter consumption observation.
|
|
548
|
+
///
|
|
549
|
+
/// This is diagnostic metadata only; it does not change delivery semantics.
|
|
550
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
551
|
+
pub enum SubmitConsumptionReason {
|
|
552
|
+
/// The token/input identity disappeared in the consumption capture.
|
|
553
|
+
ConsumptionFromCapture,
|
|
554
|
+
/// Cursor's bounded polling observed a provider-busy signal.
|
|
555
|
+
CursorPollingBusy,
|
|
556
|
+
/// The final fallback capture observed a provider-busy signal.
|
|
557
|
+
FallbackBusy,
|
|
558
|
+
/// The payload had no token marker, so no structural consumption probe ran.
|
|
559
|
+
NoMarker,
|
|
560
|
+
/// The bounded probe did not establish consumption.
|
|
561
|
+
Unverified,
|
|
562
|
+
/// An older or otherwise uncovered path did not provide a reason.
|
|
563
|
+
#[default]
|
|
564
|
+
Unknown,
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
impl SubmitConsumptionReason {
|
|
568
|
+
pub fn as_str(self) -> &'static str {
|
|
569
|
+
match self {
|
|
570
|
+
Self::ConsumptionFromCapture => "consumption_from_capture",
|
|
571
|
+
Self::CursorPollingBusy => "cursor_poll_busy",
|
|
572
|
+
Self::FallbackBusy => "fallback_busy",
|
|
573
|
+
Self::NoMarker => "no_marker",
|
|
574
|
+
Self::Unverified => "unverified",
|
|
575
|
+
Self::Unknown => "unknown",
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
508
580
|
/// Last capture sample of the inject token path. Distinguishes "read failed"
|
|
509
581
|
/// from "read succeeded and token was absent". Does not change delivery gates.
|
|
510
582
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
@@ -909,6 +981,12 @@ pub trait Transport: Send + Sync {
|
|
|
909
981
|
/// pane 存活三态(`PaneLiveness`,bug-085 穷尽 match;unknown ≠ dead ≠ live)。
|
|
910
982
|
fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError>;
|
|
911
983
|
|
|
984
|
+
/// Exit status recorded by the provider's supervising wrapper, never inferred
|
|
985
|
+
/// from terminal text. None means running or no authoritative receipt.
|
|
986
|
+
fn provider_exit_status(&self, _pane: &PaneId) -> Result<Option<u8>, TransportError> {
|
|
987
|
+
Ok(None)
|
|
988
|
+
}
|
|
989
|
+
|
|
912
990
|
/// Cheap direct pane existence check when a backend can prove it. `Ok(None)`
|
|
913
991
|
/// preserves the existing Unknown boundary.
|
|
914
992
|
fn has_pane(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
@@ -51,6 +51,59 @@ use crate::conpty::ConPtyBackend;
|
|
|
51
51
|
use crate::tmux_backend::TmuxBackend;
|
|
52
52
|
use crate::transport::{BackendKind, Transport};
|
|
53
53
|
|
|
54
|
+
#[cfg(test)]
|
|
55
|
+
thread_local! {
|
|
56
|
+
static LEADER_ENDPOINT_TRANSPORT_OVERRIDE: std::cell::RefCell<
|
|
57
|
+
Option<(String, crate::transport::test_support::OfflineTransport)>,
|
|
58
|
+
> = std::cell::RefCell::new(None);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Production live-probe transport for a recorded leader endpoint.
|
|
62
|
+
/// `classify_leader_binding`, restart `transport_for_receiver`, and
|
|
63
|
+
/// `attach_leader_targets` all go through this function.
|
|
64
|
+
pub fn leader_endpoint_transport(endpoint: &str) -> Box<dyn Transport> {
|
|
65
|
+
#[cfg(test)]
|
|
66
|
+
if let Some(injected) = LEADER_ENDPOINT_TRANSPORT_OVERRIDE.with(|slot| {
|
|
67
|
+
slot.borrow().as_ref().and_then(|(expected, transport)| {
|
|
68
|
+
(expected == endpoint).then(|| Box::new(transport.clone()) as Box<dyn Transport>)
|
|
69
|
+
})
|
|
70
|
+
}) {
|
|
71
|
+
return injected;
|
|
72
|
+
}
|
|
73
|
+
if endpoint.is_empty() || endpoint == "default" {
|
|
74
|
+
Box::new(tmux_default_transport())
|
|
75
|
+
} else {
|
|
76
|
+
Box::new(tmux_endpoint_transport(endpoint))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[cfg(test)]
|
|
81
|
+
struct LeaderEndpointTransportGuard {
|
|
82
|
+
previous: Option<(String, crate::transport::test_support::OfflineTransport)>,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
impl Drop for LeaderEndpointTransportGuard {
|
|
87
|
+
fn drop(&mut self) {
|
|
88
|
+
LEADER_ENDPOINT_TRANSPORT_OVERRIDE.with(|slot| {
|
|
89
|
+
*slot.borrow_mut() = self.previous.take();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[cfg(test)]
|
|
95
|
+
pub fn with_leader_endpoint_transport<T>(
|
|
96
|
+
endpoint: &str,
|
|
97
|
+
transport: crate::transport::test_support::OfflineTransport,
|
|
98
|
+
f: impl FnOnce() -> T,
|
|
99
|
+
) -> T {
|
|
100
|
+
let previous = LEADER_ENDPOINT_TRANSPORT_OVERRIDE.with(|slot| {
|
|
101
|
+
slot.replace(Some((endpoint.to_string(), transport)))
|
|
102
|
+
});
|
|
103
|
+
let _guard = LeaderEndpointTransportGuard { previous };
|
|
104
|
+
f()
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
/// User-visible backend selector on the CLI (`--backend tmux|conpty`).
|
|
55
108
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
56
109
|
pub enum RequestedTransportBackend {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.80",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.80",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.80",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.80"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"npm/bincheck.mjs",
|
|
35
35
|
"npm/install.mjs",
|
|
36
|
-
"npm/check-skill-links.mjs",
|
|
37
36
|
"skills",
|
|
38
37
|
"templates",
|
|
39
38
|
"examples",
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
name: team-agent
|
|
3
3
|
description: Use only when the user explicitly asks to start, operate, inspect, shutdown, or restart a Team Agent team. Treat the team-agent CLI as a sealed appliance.
|
|
4
4
|
requires_team_agent: ">=0.5.0"
|
|
5
|
-
last_verified_against: "0.5.
|
|
5
|
+
last_verified_against: "0.5.66"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Team Agent
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Sealed appliance for someone who just got the CLI. Operator handbook (permissions, models, routing, recovery): `docs/reference/team-agent-operator.md`.
|
|
11
11
|
|
|
12
|
-
If `team-agent --version` does not match `last_verified_against`,
|
|
12
|
+
If `team-agent --version` does not match `last_verified_against`, do not copy examples as current truth. Learn from the live CLI:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
team-agent --version
|
|
@@ -17,24 +17,52 @@ team-agent --help
|
|
|
17
17
|
team-agent doctor --help
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**Launch** from a tmux-addressable pane: `team-agent claude` or `team-agent codex`, then `team-agent quick-start .team/current`. Do not start a real team from a naked terminal. Existing tmux/Ghostty layouts are valid if `quick-start` runs from the leader pane.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
**Operate**
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
- Dispatch: `team-agent send TO MESSAGE` (positional TO; `--watch-result` is deprecated). After success, do not poll with `sleep` / `status` / `inbox` / `collect`.
|
|
25
|
+
TO has two co-equal logical forms: an **in-team short name** (`team-agent send reviewer "..."`) and a fully qualified `<workspace>::<team>/<agent>`. Use the qualified form across workspaces.
|
|
26
|
+
- Inspect: `team-agent status` / `status --json`. `ok: true` plus `ready: false` is not a crash.
|
|
27
|
+
- Lifecycle: `restart .` resumes a stopped team; `add-agent NAME --role-file FILE` adds or `--force` recreates one worker; `shutdown --workspace .` stops. Do not shutdown the whole team to add a worker.
|
|
28
|
+
- Roles: every `agents/*.md` must declare boolean `dangerously_skip_permissions`. Never rewrite a user-supplied model id. Never read `.env` files.
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
**On failure:** if the CLI prints a structured `action`, run that `action` first, then stop. Do not guess flags. `coordinator.session_missing` is a self-healing transient — re-check `status --json`; do not shutdown because of it.
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
The current user-facing agent is the leader (orchestrate only). Workers call `report_result` exactly once. Nested teams: `skills/team-agent/references/team-in-team.md`.
|
|
31
33
|
|
|
32
|
-
##
|
|
34
|
+
## Provider Capability Matrix
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
- `quick-start` / `restart` / `claim-leader` JSON is compact by default (`ok`, `status`/`reason`, next action, attach/send, and `readiness.all_workers_spawned`); pass `--detail` only for internal diagnostics.
|
|
37
|
-
- On a structured failure with an `action`, run that action first, then stop. Never guess flags.
|
|
38
|
-
- Never rewrite a user-supplied model id or read `.env` files.
|
|
36
|
+
Claude / Codex / Copilot / Gemini / fake: `docs/reference/team-agent-operator.md`. These two are in the runtime but were missing from that table:
|
|
37
|
+
`quick-start` / `restart` / `claim-leader` JSON is compact by default (`ok`, `status`/`reason`, next action, attach/send, and `readiness.all_workers_spawned`); pass `--detail` only for internal diagnostics.
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
| Provider | Resume | Turn-state detection | Per-worker model override | Native session fork |
|
|
40
|
+
|---|---|---|---|---|
|
|
41
|
+
| `grok` | yes (`--resume <id>`, archive-gated) | no | yes (role `model` required) | yes (`--fork-session` + new `--session-id`) |
|
|
42
|
+
| `cursor_agent` | yes (argv `--resume <chatId>`, archive-gated) | no | required on role; same-family catalog id can take effect; unknown id silent-fallback; pane chrome ≠ proven live | **no — `CapabilityUnsupported`** |
|
|
43
|
+
|
|
44
|
+
Grok / `cursor_agent` have no JSONL turn-state reader (classify → Unknown).
|
|
45
|
+
|
|
46
|
+
## Provider Prep
|
|
47
|
+
|
|
48
|
+
### Cursor provider notes
|
|
49
|
+
|
|
50
|
+
Frontmatter: `provider: cursor_agent` (not `cursor`; launcher verb is `team-agent cursor`), `auth_mode: subscription`, `name:` required (omit → `missing front matter field name`). Also required: `role:`, `tools:`, `dangerously_skip_permissions:` (bool). Subscription needs no `profile`.
|
|
51
|
+
|
|
52
|
+
`model:` is required (omit compile-fails; blocks a silent builtin `sonnet-4-thinking`). The flag stays on argv. Same-family catalog ids can change pane chrome. An id not in that provider's catalog silent-falls back (landing not stable; no events/stderr/pane warning). Pick names from the catalog; after spawn, `capture-pane` once for chrome. Do not treat the role field or pane chrome as proof of the live model.
|
|
53
|
+
|
|
54
|
+
One `cursor_agent` per workspace. A second seat (including `add-agent --force` of a different id) fail-closes:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
error: cursor_agent seat already occupies this workspace
|
|
58
|
+
reason: <workspace>/.cursor/mcp.json is directory-scoped; a second seat overwrites TEAM_AGENT_ID (last-writer)
|
|
59
|
+
action: do not add another CursorAgent in this workspace until per-seat MCP identity is isolated
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Several cursor seats → one workspace directory each. Same seat, fresh context → `reset-agent --discard-session`.
|
|
63
|
+
|
|
64
|
+
`clone-agent` copies the source role (provider unchanged). Runtime add: `clone-agent` → `stop-agent` → `remove-agent --confirm` (deletes `.team/dynamic-role-files/`) → write the role file → `add-agent --role-file` → dispatch.
|
|
65
|
+
|
|
66
|
+
- Restart emits `--resume <chatId>` when `store.db`/`meta.json` exist; the gate does not read chat text. Persist anything that must survive restart.
|
|
67
|
+
- Delivery sends one Enter; a second Enter interrupts the turn.
|
|
68
|
+
- After spawn, the pane footer should show `Cursor Agent v<version>`. Do not use `strings` to probe the binary.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"authority": {
|
|
4
4
|
"kind": "normative_handbook_plus_live_help",
|
|
5
5
|
"handbook": {
|
|
6
|
-
"path": "
|
|
6
|
+
"path": "docs/reference/team-agent-operator.md",
|
|
7
7
|
"start_marker": "<!-- command-coverage:normative-start -->",
|
|
8
8
|
"end_marker": "<!-- command-coverage:normative-end -->"
|
|
9
9
|
},
|
|
@@ -67,13 +67,7 @@
|
|
|
67
67
|
"owner": "runtime-owner:leader-channel",
|
|
68
68
|
"plan": "Execute in a hermetic leader fixture and bind assertions to owner/receiver persistence or a precise zero-write refusal."
|
|
69
69
|
},
|
|
70
|
-
|
|
71
|
-
"command": "team-agent claim-leader --detail",
|
|
72
|
-
"bucket": "declared_gap",
|
|
73
|
-
"covered": false,
|
|
74
|
-
"owner": "runtime-owner:leader-channel",
|
|
75
|
-
"plan": "Execute the exact argv and bind assertions to detail-only topology_convergence, owner_epoch, and checked_paths fields."
|
|
76
|
-
},
|
|
70
|
+
|
|
77
71
|
{
|
|
78
72
|
"command": "team-agent claude",
|
|
79
73
|
"bucket": "declared_gap",
|
|
@@ -240,13 +234,7 @@
|
|
|
240
234
|
}
|
|
241
235
|
}
|
|
242
236
|
},
|
|
243
|
-
|
|
244
|
-
"command": "team-agent quick-start --json --detail",
|
|
245
|
-
"bucket": "declared_gap",
|
|
246
|
-
"covered": false,
|
|
247
|
-
"owner": "runtime-owner:lifecycle",
|
|
248
|
-
"plan": "Execute the exact argv and bind assertions to detail-only receiver, topology, and coordinator diagnostics while keeping compact default JSON."
|
|
249
|
-
},
|
|
237
|
+
|
|
250
238
|
{
|
|
251
239
|
"command": "team-agent quick-start <dir>",
|
|
252
240
|
"bucket": "covered",
|
|
@@ -322,13 +310,7 @@
|
|
|
322
310
|
"owner": "runtime-owner:restart",
|
|
323
311
|
"plan": "Execute a representative team selector and bind assertions to selected-team restart and sibling preservation."
|
|
324
312
|
},
|
|
325
|
-
|
|
326
|
-
"command": "team-agent restart --json --detail",
|
|
327
|
-
"bucket": "declared_gap",
|
|
328
|
-
"covered": false,
|
|
329
|
-
"owner": "runtime-owner:restart",
|
|
330
|
-
"plan": "Execute the exact argv and bind assertions to detail-only coordinator diagnostics while keeping compact default JSON."
|
|
331
|
-
},
|
|
313
|
+
|
|
332
314
|
{
|
|
333
315
|
"command": "team-agent send --task task_initial \"Start\"",
|
|
334
316
|
"bucket": "declared_gap",
|
|
@@ -357,13 +339,6 @@
|
|
|
357
339
|
"owner": "runtime-owner:messaging",
|
|
358
340
|
"plan": "Execute the compact-skill positional send smoke with representative target and message tokens, then bind delivery truth."
|
|
359
341
|
},
|
|
360
|
-
{
|
|
361
|
-
"command": "team-agent send AGENT MESSAGE",
|
|
362
|
-
"bucket": "declared_gap",
|
|
363
|
-
"covered": false,
|
|
364
|
-
"owner": "runtime-owner:messaging",
|
|
365
|
-
"plan": "Execute a representative positional send with explicit agent and message tokens, then bind recipient and delivery truth."
|
|
366
|
-
},
|
|
367
342
|
{
|
|
368
343
|
"command": "team-agent send reviewer \"...\"",
|
|
369
344
|
"bucket": "declared_gap",
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/** Resolve local Markdown links and optionally validate package/installed inventories. */
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
const args = process.argv.slice(2);
|
|
7
|
-
const value = (name) => { const i = args.indexOf(name); return i < 0 ? null : args[i + 1]; };
|
|
8
|
-
const rootArg = value("--root") ?? "skills/team-agent";
|
|
9
|
-
const root = path.resolve(rootArg);
|
|
10
|
-
const installedArg = value("--installed");
|
|
11
|
-
const inventoryArg = value("--inventory");
|
|
12
|
-
const markdownLink = /!?(?:\[[^\]]*\])\(([^)]+)\)/g;
|
|
13
|
-
const files = [];
|
|
14
|
-
const fail = (message) => { throw new Error(message); };
|
|
15
|
-
const real = (file) => { try { return fs.realpathSync.native(file); } catch (error) { fail(`cannot resolve ${file}: ${error.message}`); } };
|
|
16
|
-
const rootReal = real(root);
|
|
17
|
-
const inside = (candidate, base = rootReal) => candidate === base || candidate.startsWith(`${base}${path.sep}`);
|
|
18
|
-
const lexicalInside = (candidate, base = root) => candidate === base || candidate.startsWith(`${base}${path.sep}`);
|
|
19
|
-
|
|
20
|
-
function walk(dir, boundary = rootReal) {
|
|
21
|
-
let entries;
|
|
22
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)); }
|
|
23
|
-
catch (error) { fail(`cannot read ${dir}: ${error.message}`); }
|
|
24
|
-
for (const entry of entries) {
|
|
25
|
-
const file = path.join(dir, entry.name);
|
|
26
|
-
const fileReal = real(file);
|
|
27
|
-
if (!inside(fileReal, boundary)) fail(`${file}: realpath escapes tree`);
|
|
28
|
-
if (entry.isDirectory()) walk(file, boundary);
|
|
29
|
-
else if (entry.isFile() && file.endsWith(".md")) files.push(file);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function normalizeFragment(fragment) {
|
|
33
|
-
return decodeURIComponent(fragment).toLowerCase().trim().replace(/<[^>]+>/g, "")
|
|
34
|
-
.replace(/[`*_~]/g, "").replace(/[^\p{L}\p{N} -]/gu, "").replace(/\s+/g, "-");
|
|
35
|
-
}
|
|
36
|
-
function anchors(text) {
|
|
37
|
-
const found = new Set();
|
|
38
|
-
const used = new Map();
|
|
39
|
-
const add = (value) => {
|
|
40
|
-
const base = normalizeFragment(value); if (!base) return;
|
|
41
|
-
const n = used.get(base) ?? 0; used.set(base, n + 1); found.add(n ? `${base}-${n}` : base);
|
|
42
|
-
};
|
|
43
|
-
for (const m of text.matchAll(/^(?: {0,3})#{1,6}\s+(.+?)\s*#*\s*$/gm) ) add(m[1]);
|
|
44
|
-
for (const m of text.matchAll(/^(?: {0,3})([^\n#]+)\n(?:=+|-+)\s*$/gm)) add(m[1]);
|
|
45
|
-
for (const m of text.matchAll(/<(?:a|[^>]+)\s+[^>]*(?:id|name)=["']([^"']+)["'][^>]*>/gi)) add(m[1]);
|
|
46
|
-
return found;
|
|
47
|
-
}
|
|
48
|
-
function checkTree(dir, baseRoot, baseReal) {
|
|
49
|
-
files.length = 0; walk(dir, baseReal);
|
|
50
|
-
const errors = [];
|
|
51
|
-
for (const file of [...files].sort()) {
|
|
52
|
-
const text = fs.readFileSync(file, "utf8");
|
|
53
|
-
const known = anchors(text);
|
|
54
|
-
for (const match of text.matchAll(markdownLink)) {
|
|
55
|
-
const href = match[1].trim();
|
|
56
|
-
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
57
|
-
const hash = href.indexOf("#");
|
|
58
|
-
const rawPath = hash < 0 ? href : href.slice(0, hash);
|
|
59
|
-
const fragment = hash < 0 ? "" : href.slice(hash + 1).split("?", 1)[0];
|
|
60
|
-
const target = rawPath ? path.resolve(path.dirname(file), rawPath) : file;
|
|
61
|
-
if (!lexicalInside(target, baseRoot)) { errors.push(`${file}: lexical target escapes tree: ${href}`); continue; }
|
|
62
|
-
const targetReal = real(target);
|
|
63
|
-
if (!inside(targetReal, baseReal)) { errors.push(`${file}: realpath target escapes tree: ${href}`); continue; }
|
|
64
|
-
if (!fs.statSync(targetReal).isFile()) { errors.push(`${file}: target is not a file: ${href}`); continue; }
|
|
65
|
-
if (fragment && targetReal.endsWith(".md")) {
|
|
66
|
-
const targetAnchors = targetReal === real(file) ? known : anchors(fs.readFileSync(targetReal, "utf8"));
|
|
67
|
-
if (!targetAnchors.has(normalizeFragment(fragment))) errors.push(`${file}: missing fragment: ${href}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (errors.length) fail(errors.sort().join("\n"));
|
|
72
|
-
return [...files].sort().length;
|
|
73
|
-
}
|
|
74
|
-
const count = checkTree(root, root, rootReal);
|
|
75
|
-
if (inventoryArg) {
|
|
76
|
-
const inventory = path.resolve(inventoryArg);
|
|
77
|
-
for (const relative of fs.readFileSync(inventory, "utf8").split(/\r?\n/).filter(Boolean).sort()) {
|
|
78
|
-
if (path.isAbsolute(relative)) fail(`inventory path must be relative: ${relative}`);
|
|
79
|
-
const candidate = path.resolve(root, relative);
|
|
80
|
-
if (!lexicalInside(candidate)) fail(`inventory path escapes tree: ${relative}`);
|
|
81
|
-
const candidateReal = real(candidate);
|
|
82
|
-
if (!inside(candidateReal)) fail(`inventory realpath escapes tree: ${relative}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (installedArg) {
|
|
86
|
-
const installed = path.resolve(installedArg); console.log(`checked ${checkTree(installed, installed, real(installed))} Markdown files in ${installed}`);
|
|
87
|
-
}
|
|
88
|
-
console.log(`checked ${count} Markdown files in ${root}`);
|