1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! P5-6 (COMPOSABLE-HARNESS-DESIGN.md §2 module 4 `tools.background`: "D1
//! background exec + monitor/event feed; D10 bg-manager; D3 self-paced/
//! scheduled loops"; §2.1 "tools.background → permissions.approvals
//! (auto-policy) [C6 as dep]"; §2.2 C6): the pure, agent-independent data
//! shapes and bounded-buffer arithmetic a runtime agent's
//! `background_exec`/`background_status`/`background_list`/`background_kill`
//! intrinsics build on — kept separate from `agent.rs` so the bounded-
//! capture truncation logic and job-id shape are unit-testable without a
//! full `Agent`/mock-`Provider`/real-subprocess harness, the same
//! "pure config → set, testable without the loop" precedent
//! subagent runtime documents for itself (P5-3).
//!
//! **Activation.** Everything here is inert until `Agent` actually consults
//! it, which only happens when `Config::tools_background_enabled` is `true`
//! (`capabilities.tools_background.enabled`, default `false`) — importing
//! this module changes nothing for an agent that never turns the module on.
//!
//! **Process-kill reuse.** The actual OS-process spawn/kill machinery lives
//! in the agent loop (it needs `tokio::process::Command`/`Child`, which this
//! module deliberately does not depend on, keeping it synchronous and
//! trivially unit-testable). The concurrency bound reuses
//! the subagent concurrency guard verbatim — the same
//! generic `Arc<AtomicUsize>` gauge machinery, just a second, independent
//! gauge instance scoped to background JOBS rather than subagent SPAWNS
//! (`Agent::background_concurrency_gauge`, distinct from
//! `Agent::subagent_concurrency_gauge`).
use ;
use Mutex;
/// P5-6 (build brief "cap the buffer like P5-2's 16MiB caps"): the default
/// per-job bounded-capture ceiling, matching `crate::mcp::MCP_MAX_RESPONSE_BYTES`'s
/// hardening precedent — generous for real command output while bounding
/// how much memory one background job (let alone `max_concurrent` of them
/// at once) can force this process to hold.
pub const DEFAULT_MAX_OUTPUT_BYTES: usize = 16 * 1024 * 1024;
/// P5-6 (resource bound, mirroring `crate::subagents`'s "max concurrent...
/// cap, fail-closed... configurable" precedent): the default maximum number
/// of background jobs this agent may have in flight at once.
pub const DEFAULT_MAX_CONCURRENT: usize = 4;
/// A background job's run state, as observed by `background_status`/
/// `background_list`.
/// Bounded, incrementally-appended output capture shared (via `Arc`)
/// between a job's stdout/stderr reader tasks and whatever later polls it
/// (`background_status`/`background_list`). Thread-safe; every method is
/// fail-soft on a poisoned lock (treats it as "temporarily unavailable",
/// the same posture `crate::permissions::approval::ApprovalCache` already
/// documents for itself) rather than panicking a reader task or a tool
/// call.
/// P5-6: process-wide sequence number backing [`next_job_id`] —
/// disambiguates two jobs spawned in the same millisecond, mirroring
/// `crate::agent`'s own `SUBAGENT_ID_SEQ` precedent (kept as a second,
/// independent counter rather than sharing that one, since a job id and a
/// subagent id are never compared against each other).
static JOB_ID_SEQ: AtomicU64 = new;
/// A fresh, process-unique background job id (`"bg-<hex-ts>-<hex-seq>"`),
/// given the caller's own millisecond timestamp (kept as a parameter rather
/// than reading the clock in here, so this stays a pure function for the
/// unit tests below).