@team-agent/installer 0.3.1 → 0.3.3
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 +34 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +234 -26
- package/crates/team-agent/src/cli/diagnose.rs +144 -10
- package/crates/team-agent/src/cli/emit.rs +289 -54
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +1281 -196
- package/crates/team-agent/src/cli/status_port.rs +195 -46
- package/crates/team-agent/src/cli/tests/divergence.rs +1 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +23 -13
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +59 -3
- package/crates/team-agent/src/cli/types.rs +18 -0
- package/crates/team-agent/src/compiler.rs +15 -5
- package/crates/team-agent/src/coordinator/health.rs +95 -17
- package/crates/team-agent/src/coordinator/mod.rs +4 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
- package/crates/team-agent/src/coordinator/tick.rs +222 -69
- package/crates/team-agent/src/coordinator/types.rs +15 -3
- package/crates/team-agent/src/db/schema.rs +37 -2
- package/crates/team-agent/src/diagnose/comms.rs +226 -0
- package/crates/team-agent/src/diagnose/mod.rs +45 -0
- package/crates/team-agent/src/diagnose/orphans.rs +658 -0
- package/crates/team-agent/src/fake_worker.rs +146 -3
- package/crates/team-agent/src/leader/start.rs +121 -23
- package/crates/team-agent/src/leader/types.rs +44 -1
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +645 -47
- package/crates/team-agent/src/lifecycle/launch.rs +1061 -146
- package/crates/team-agent/src/lifecycle/mod.rs +2 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +99 -23
- package/crates/team-agent/src/lifecycle/restart/common.rs +183 -24
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +498 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +27 -7
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
- package/crates/team-agent/src/lifecycle/restart.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +37 -7
- package/crates/team-agent/src/lifecycle/types.rs +19 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
- package/crates/team-agent/src/mcp_server/mod.rs +3 -74
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
- package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
- package/crates/team-agent/src/mcp_server/tools.rs +312 -111
- package/crates/team-agent/src/mcp_server/types.rs +6 -4
- package/crates/team-agent/src/mcp_server/wire.rs +19 -7
- package/crates/team-agent/src/message_store.rs +21 -4
- package/crates/team-agent/src/messaging/delivery.rs +470 -59
- package/crates/team-agent/src/messaging/mod.rs +9 -6
- package/crates/team-agent/src/messaging/results.rs +353 -63
- package/crates/team-agent/src/messaging/selftest.rs +199 -12
- package/crates/team-agent/src/messaging/send.rs +35 -3
- package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
- package/crates/team-agent/src/messaging/types.rs +11 -3
- package/crates/team-agent/src/os_probe.rs +119 -0
- package/crates/team-agent/src/packaging/migrate.rs +10 -2
- package/crates/team-agent/src/packaging/tests.rs +23 -0
- package/crates/team-agent/src/provider/adapter.rs +564 -63
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
- package/crates/team-agent/src/provider/classify.rs +51 -4
- package/crates/team-agent/src/provider/helpers.rs +10 -1
- package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
- package/crates/team-agent/src/provider/types.rs +47 -0
- package/crates/team-agent/src/session_capture.rs +616 -0
- package/crates/team-agent/src/state/persist.rs +170 -1
- package/crates/team-agent/src/state/projection.rs +141 -8
- package/crates/team-agent/src/state/selector.rs +5 -2
- package/crates/team-agent/src/tmux_backend.rs +161 -64
- package/crates/team-agent/src/transport/test_support.rs +9 -0
- package/crates/team-agent/src/transport/tests/wire.rs +4 -0
- package/crates/team-agent/src/transport.rs +13 -2
- package/package.json +4 -4
|
@@ -5,8 +5,9 @@ use std::process::Command;
|
|
|
5
5
|
|
|
6
6
|
use super::helpers::{find_session_id, parse_jsonl_records, patterns};
|
|
7
7
|
use super::types::{
|
|
8
|
-
AuthHintStatus, CaptureVia, CapturedSession, Confidence, McpConfig,
|
|
9
|
-
ProviderError, RolloutPath, SessionId,
|
|
8
|
+
AuthHintStatus, CaptureVia, CapturedSession, CommandPlan, Confidence, McpConfig,
|
|
9
|
+
ProviderCaps, ProviderCommandContext, ProviderError, RolloutPath, SessionId,
|
|
10
|
+
StatusPatterns,
|
|
10
11
|
};
|
|
11
12
|
use super::{AuthMode, Provider};
|
|
12
13
|
|
|
@@ -57,6 +58,20 @@ pub trait ProviderAdapter {
|
|
|
57
58
|
tools: &[&str],
|
|
58
59
|
) -> Result<Vec<String>, ProviderError>;
|
|
59
60
|
|
|
61
|
+
fn build_command_plan(
|
|
62
|
+
&self,
|
|
63
|
+
ctx: ProviderCommandContext<'_>,
|
|
64
|
+
) -> Result<CommandPlan, ProviderError> {
|
|
65
|
+
self.build_command_with_tools(
|
|
66
|
+
ctx.auth_mode,
|
|
67
|
+
ctx.mcp_config,
|
|
68
|
+
ctx.system_prompt,
|
|
69
|
+
ctx.model,
|
|
70
|
+
ctx.tools,
|
|
71
|
+
)
|
|
72
|
+
.map(CommandPlan::argv_only)
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
/// 启动后从 provider session 日志捕获 session_id + rollout_path
|
|
61
76
|
/// (`claude.py:73`/`codex.py:62`)。fs watch / mtime fallback / repair。
|
|
62
77
|
fn capture_session_id(
|
|
@@ -66,6 +81,25 @@ pub trait ProviderAdapter {
|
|
|
66
81
|
timeout_s: u64,
|
|
67
82
|
) -> Result<Option<CapturedSession>, ProviderError>;
|
|
68
83
|
|
|
84
|
+
/// Internal capture surface for same-team multi-agent attribution: enumerate every
|
|
85
|
+
/// cwd-matching provider transcript candidate, then let the runtime allocate them
|
|
86
|
+
/// once per tick/restart pass using per-agent context.
|
|
87
|
+
fn capture_session_candidates(
|
|
88
|
+
&self,
|
|
89
|
+
context: &CaptureSessionContext,
|
|
90
|
+
timeout_s: u64,
|
|
91
|
+
) -> Result<Vec<CapturedSessionCandidate>, ProviderError> {
|
|
92
|
+
Ok(self
|
|
93
|
+
.capture_session_id(&context.agent_id, &context.spawn_cwd, timeout_s)?
|
|
94
|
+
.into_iter()
|
|
95
|
+
.map(|captured| CapturedSessionCandidate {
|
|
96
|
+
captured,
|
|
97
|
+
positive_agent_id_match: false,
|
|
98
|
+
agent_path_match: false,
|
|
99
|
+
})
|
|
100
|
+
.collect())
|
|
101
|
+
}
|
|
102
|
+
|
|
69
103
|
/// restart/reset 路径:从已存 transcript/rollout 回收 session_id
|
|
70
104
|
/// (`claude.py:115`)。`None` 合法(找不到)。
|
|
71
105
|
fn recover_session_id(
|
|
@@ -100,6 +134,22 @@ pub trait ProviderAdapter {
|
|
|
100
134
|
tools: &[&str],
|
|
101
135
|
) -> Result<Vec<String>, ProviderError>;
|
|
102
136
|
|
|
137
|
+
fn build_resume_command_plan(
|
|
138
|
+
&self,
|
|
139
|
+
session_id: Option<&SessionId>,
|
|
140
|
+
ctx: ProviderCommandContext<'_>,
|
|
141
|
+
) -> Result<CommandPlan, ProviderError> {
|
|
142
|
+
self.build_resume_command_with_context(
|
|
143
|
+
session_id,
|
|
144
|
+
ctx.auth_mode,
|
|
145
|
+
ctx.mcp_config,
|
|
146
|
+
ctx.system_prompt,
|
|
147
|
+
ctx.model,
|
|
148
|
+
ctx.tools,
|
|
149
|
+
)
|
|
150
|
+
.map(CommandPlan::argv_only)
|
|
151
|
+
}
|
|
152
|
+
|
|
103
153
|
/// 构造 fork 命令(`providers.py:99`)。fork 需 caps.fork ∧ auth_mode!=compatible_api;
|
|
104
154
|
/// 不支持 → `Err`。
|
|
105
155
|
fn fork(
|
|
@@ -119,6 +169,22 @@ pub trait ProviderAdapter {
|
|
|
119
169
|
tools: &[&str],
|
|
120
170
|
) -> Result<Vec<String>, ProviderError>;
|
|
121
171
|
|
|
172
|
+
fn fork_plan(
|
|
173
|
+
&self,
|
|
174
|
+
session_id: Option<&SessionId>,
|
|
175
|
+
ctx: ProviderCommandContext<'_>,
|
|
176
|
+
) -> Result<CommandPlan, ProviderError> {
|
|
177
|
+
self.fork_with_context(
|
|
178
|
+
session_id,
|
|
179
|
+
ctx.auth_mode,
|
|
180
|
+
ctx.mcp_config,
|
|
181
|
+
ctx.system_prompt,
|
|
182
|
+
ctx.model,
|
|
183
|
+
ctx.tools,
|
|
184
|
+
)
|
|
185
|
+
.map(CommandPlan::argv_only)
|
|
186
|
+
}
|
|
187
|
+
|
|
122
188
|
/// 计算本 provider 该用的 MCP server 配置(`adapter.py` mcp_config;claude
|
|
123
189
|
/// compatible_api 走 `ensure_compatible_claude_mcp_config`)。
|
|
124
190
|
fn mcp_config(&self, auth_mode: AuthMode) -> Result<McpConfig, ProviderError>;
|
|
@@ -134,8 +200,9 @@ pub trait ProviderAdapter {
|
|
|
134
200
|
/// 校验 model 名对本 provider 合法(`codex debug models` 等;doctor)。
|
|
135
201
|
fn validate_model(&self, model: &str) -> Result<bool, ProviderError>;
|
|
136
202
|
|
|
137
|
-
/// Provider-specific startup prompt handling.
|
|
138
|
-
///
|
|
203
|
+
/// Provider-specific startup prompt handling. Codex and Claude delegate to
|
|
204
|
+
/// provider-layer recognizers; providers without startup prompts return an
|
|
205
|
+
/// empty list.
|
|
139
206
|
fn handle_startup_prompts(
|
|
140
207
|
&self,
|
|
141
208
|
transport: &dyn crate::transport::Transport,
|
|
@@ -147,12 +214,35 @@ pub trait ProviderAdapter {
|
|
|
147
214
|
Provider::Codex => {
|
|
148
215
|
super::startup_prompt::codex_handle_startup_prompts(transport, target, checks, sleep_s)
|
|
149
216
|
}
|
|
217
|
+
Provider::Claude | Provider::ClaudeCode => {
|
|
218
|
+
super::startup_prompt::claude_handle_startup_prompts(
|
|
219
|
+
transport, target, checks, sleep_s,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
150
222
|
_ => Vec::new(),
|
|
151
223
|
}))
|
|
152
224
|
.unwrap_or_default()
|
|
153
225
|
}
|
|
154
226
|
}
|
|
155
227
|
|
|
228
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
229
|
+
pub struct CaptureSessionContext {
|
|
230
|
+
pub agent_id: String,
|
|
231
|
+
pub spawn_cwd: PathBuf,
|
|
232
|
+
pub pane_id: Option<String>,
|
|
233
|
+
pub pane_pid: Option<u32>,
|
|
234
|
+
pub spawned_at: Option<String>,
|
|
235
|
+
pub expected_session_id: Option<SessionId>,
|
|
236
|
+
pub provider_projects_root: Option<PathBuf>,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
240
|
+
pub struct CapturedSessionCandidate {
|
|
241
|
+
pub captured: CapturedSession,
|
|
242
|
+
pub positive_agent_id_match: bool,
|
|
243
|
+
pub agent_path_match: bool,
|
|
244
|
+
}
|
|
245
|
+
|
|
156
246
|
// ===========================================================================
|
|
157
247
|
// FACADE 自由函数 (doc §71 providers.get_adapter — body unimplemented)
|
|
158
248
|
// ===========================================================================
|
|
@@ -230,9 +320,12 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
230
320
|
}
|
|
231
321
|
|
|
232
322
|
fn auth_hint(&self, auth_mode: AuthMode) -> AuthHintStatus {
|
|
233
|
-
match
|
|
234
|
-
|
|
235
|
-
|
|
323
|
+
match self.provider {
|
|
324
|
+
Provider::Claude | Provider::ClaudeCode => claude_auth_hint(auth_mode),
|
|
325
|
+
_ => match auth_mode {
|
|
326
|
+
AuthMode::Subscription => AuthHintStatus::Present,
|
|
327
|
+
AuthMode::OfficialApi | AuthMode::CompatibleApi => AuthHintStatus::MissingOrUnknown,
|
|
328
|
+
},
|
|
236
329
|
}
|
|
237
330
|
}
|
|
238
331
|
|
|
@@ -256,7 +349,7 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
256
349
|
) -> Result<Vec<String>, ProviderError> {
|
|
257
350
|
match self.provider {
|
|
258
351
|
Provider::Claude | Provider::ClaudeCode => {
|
|
259
|
-
Ok(claude_launch_command(self, auth_mode, mcp_config, system_prompt, model)?)
|
|
352
|
+
Ok(claude_launch_command(self, auth_mode, mcp_config, system_prompt, model, tools)?)
|
|
260
353
|
}
|
|
261
354
|
Provider::Codex => Ok(codex_base_command(None, auth_mode, mcp_config, system_prompt, model, tools)),
|
|
262
355
|
Provider::GeminiCli => {
|
|
@@ -271,47 +364,83 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
271
364
|
}
|
|
272
365
|
}
|
|
273
366
|
|
|
367
|
+
fn build_command_plan(
|
|
368
|
+
&self,
|
|
369
|
+
ctx: ProviderCommandContext<'_>,
|
|
370
|
+
) -> Result<CommandPlan, ProviderError> {
|
|
371
|
+
match self.provider {
|
|
372
|
+
Provider::Claude | Provider::ClaudeCode => {
|
|
373
|
+
let expected = next_session_token();
|
|
374
|
+
let managed = ctx.profile_launch.is_some_and(|profile| profile.managed_mcp_config);
|
|
375
|
+
let projects_root = ctx
|
|
376
|
+
.profile_launch
|
|
377
|
+
.and_then(|profile| profile.claude_projects_root.clone());
|
|
378
|
+
let model = claude_context_model(ctx);
|
|
379
|
+
let mut argv = claude_base_command(
|
|
380
|
+
self,
|
|
381
|
+
ctx.auth_mode,
|
|
382
|
+
ctx.mcp_config,
|
|
383
|
+
ctx.system_prompt,
|
|
384
|
+
model,
|
|
385
|
+
ctx.tools,
|
|
386
|
+
managed,
|
|
387
|
+
)?;
|
|
388
|
+
argv.push("--session-id".to_string());
|
|
389
|
+
argv.push(expected.clone());
|
|
390
|
+
Ok(CommandPlan {
|
|
391
|
+
argv,
|
|
392
|
+
expected_session_id: Some(SessionId::new(expected)),
|
|
393
|
+
provider_projects_root: projects_root,
|
|
394
|
+
managed_mcp_config: managed,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
_ => self
|
|
398
|
+
.build_command_with_tools(
|
|
399
|
+
ctx.auth_mode,
|
|
400
|
+
ctx.mcp_config,
|
|
401
|
+
ctx.system_prompt,
|
|
402
|
+
ctx.model,
|
|
403
|
+
ctx.tools,
|
|
404
|
+
)
|
|
405
|
+
.map(CommandPlan::argv_only),
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
274
409
|
fn capture_session_id(
|
|
275
410
|
&self,
|
|
276
411
|
agent_id: &str,
|
|
277
412
|
spawn_cwd: &Path,
|
|
278
|
-
|
|
413
|
+
timeout_s: u64,
|
|
279
414
|
) -> Result<Option<CapturedSession>, ProviderError> {
|
|
280
|
-
let
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
415
|
+
let context = CaptureSessionContext {
|
|
416
|
+
agent_id: agent_id.to_string(),
|
|
417
|
+
spawn_cwd: spawn_cwd.to_path_buf(),
|
|
418
|
+
pane_id: None,
|
|
419
|
+
pane_pid: None,
|
|
420
|
+
spawned_at: None,
|
|
421
|
+
expected_session_id: None,
|
|
422
|
+
provider_projects_root: None,
|
|
423
|
+
};
|
|
424
|
+
Ok(self
|
|
425
|
+
.capture_session_candidates(&context, timeout_s)?
|
|
426
|
+
.into_iter()
|
|
427
|
+
.next()
|
|
428
|
+
.map(|candidate| candidate.captured))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
fn capture_session_candidates(
|
|
432
|
+
&self,
|
|
433
|
+
context: &CaptureSessionContext,
|
|
434
|
+
timeout_s: u64,
|
|
435
|
+
) -> Result<Vec<CapturedSessionCandidate>, ProviderError> {
|
|
436
|
+
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(timeout_s);
|
|
437
|
+
loop {
|
|
438
|
+
let out = scan_session_candidates_once(self.provider, context)?;
|
|
439
|
+
if !out.is_empty() || timeout_s == 0 || std::time::Instant::now() >= deadline {
|
|
440
|
+
return Ok(out);
|
|
295
441
|
}
|
|
296
|
-
|
|
297
|
-
CaptureVia::FsWatch
|
|
298
|
-
} else {
|
|
299
|
-
CaptureVia::FsMtimeFallback
|
|
300
|
-
};
|
|
301
|
-
let attribution_confidence = if session_id.is_some() {
|
|
302
|
-
Confidence::High
|
|
303
|
-
} else {
|
|
304
|
-
Confidence::Low
|
|
305
|
-
};
|
|
306
|
-
return Ok(Some(CapturedSession {
|
|
307
|
-
session_id: session_id.map(SessionId::new),
|
|
308
|
-
rollout_path: Some(RolloutPath::new(path)),
|
|
309
|
-
captured_via,
|
|
310
|
-
attribution_confidence,
|
|
311
|
-
spawn_cwd: spawn_cwd.to_path_buf(),
|
|
312
|
-
}));
|
|
442
|
+
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
313
443
|
}
|
|
314
|
-
Ok(None)
|
|
315
444
|
}
|
|
316
445
|
|
|
317
446
|
fn recover_session_id(
|
|
@@ -376,7 +505,8 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
376
505
|
Ok(argv)
|
|
377
506
|
}
|
|
378
507
|
Provider::Claude | Provider::ClaudeCode => {
|
|
379
|
-
let mut argv =
|
|
508
|
+
let mut argv =
|
|
509
|
+
claude_base_command(self, auth_mode, mcp_config, system_prompt, model, tools, false)?;
|
|
380
510
|
argv.push("--resume".to_string());
|
|
381
511
|
argv.push(session_id.as_str().to_string());
|
|
382
512
|
Ok(argv)
|
|
@@ -388,6 +518,49 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
388
518
|
}
|
|
389
519
|
}
|
|
390
520
|
|
|
521
|
+
fn build_resume_command_plan(
|
|
522
|
+
&self,
|
|
523
|
+
session_id: Option<&SessionId>,
|
|
524
|
+
ctx: ProviderCommandContext<'_>,
|
|
525
|
+
) -> Result<CommandPlan, ProviderError> {
|
|
526
|
+
match self.provider {
|
|
527
|
+
Provider::Claude | Provider::ClaudeCode => {
|
|
528
|
+
let Some(session_id) = session_id else {
|
|
529
|
+
return Err(ProviderError::ResumeUnavailable("resume requires session_id".to_string()));
|
|
530
|
+
};
|
|
531
|
+
let managed = ctx.profile_launch.is_some_and(|profile| profile.managed_mcp_config);
|
|
532
|
+
let model = claude_context_model(ctx);
|
|
533
|
+
let mut argv = claude_base_command(
|
|
534
|
+
self,
|
|
535
|
+
ctx.auth_mode,
|
|
536
|
+
ctx.mcp_config,
|
|
537
|
+
ctx.system_prompt,
|
|
538
|
+
model,
|
|
539
|
+
ctx.tools,
|
|
540
|
+
managed,
|
|
541
|
+
)?;
|
|
542
|
+
argv.push("--resume".to_string());
|
|
543
|
+
argv.push(session_id.as_str().to_string());
|
|
544
|
+
let mut plan = CommandPlan::argv_only(argv);
|
|
545
|
+
plan.provider_projects_root = ctx
|
|
546
|
+
.profile_launch
|
|
547
|
+
.and_then(|profile| profile.claude_projects_root.clone());
|
|
548
|
+
plan.managed_mcp_config = managed;
|
|
549
|
+
Ok(plan)
|
|
550
|
+
}
|
|
551
|
+
_ => self
|
|
552
|
+
.build_resume_command_with_context(
|
|
553
|
+
session_id,
|
|
554
|
+
ctx.auth_mode,
|
|
555
|
+
ctx.mcp_config,
|
|
556
|
+
ctx.system_prompt,
|
|
557
|
+
ctx.model,
|
|
558
|
+
ctx.tools,
|
|
559
|
+
)
|
|
560
|
+
.map(CommandPlan::argv_only),
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
391
564
|
fn fork(
|
|
392
565
|
&self,
|
|
393
566
|
session_id: Option<&SessionId>,
|
|
@@ -429,7 +602,8 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
429
602
|
Ok(argv)
|
|
430
603
|
}
|
|
431
604
|
Provider::Claude | Provider::ClaudeCode => {
|
|
432
|
-
let mut argv =
|
|
605
|
+
let mut argv =
|
|
606
|
+
claude_base_command(self, auth_mode, mcp_config, system_prompt, model, tools, false)?;
|
|
433
607
|
argv.push("--session-id".to_string());
|
|
434
608
|
argv.push(next_session_token());
|
|
435
609
|
argv.push("--resume".to_string());
|
|
@@ -444,6 +618,62 @@ impl ProviderAdapter for BasicProviderAdapter {
|
|
|
444
618
|
}
|
|
445
619
|
}
|
|
446
620
|
|
|
621
|
+
fn fork_plan(
|
|
622
|
+
&self,
|
|
623
|
+
session_id: Option<&SessionId>,
|
|
624
|
+
ctx: ProviderCommandContext<'_>,
|
|
625
|
+
) -> Result<CommandPlan, ProviderError> {
|
|
626
|
+
match self.provider {
|
|
627
|
+
Provider::Claude | Provider::ClaudeCode => {
|
|
628
|
+
if !self.caps().fork || ctx.auth_mode == AuthMode::CompatibleApi {
|
|
629
|
+
return Err(ProviderError::CapabilityUnsupported(format!(
|
|
630
|
+
"{} does not support native session fork",
|
|
631
|
+
provider_wire(self.provider)
|
|
632
|
+
)));
|
|
633
|
+
}
|
|
634
|
+
let Some(session_id) = session_id else {
|
|
635
|
+
return Err(ProviderError::ResumeUnavailable("fork requires session_id".to_string()));
|
|
636
|
+
};
|
|
637
|
+
let expected = next_session_token();
|
|
638
|
+
let managed = ctx.profile_launch.is_some_and(|profile| profile.managed_mcp_config);
|
|
639
|
+
let projects_root = ctx
|
|
640
|
+
.profile_launch
|
|
641
|
+
.and_then(|profile| profile.claude_projects_root.clone());
|
|
642
|
+
let model = claude_context_model(ctx);
|
|
643
|
+
let mut argv = claude_base_command(
|
|
644
|
+
self,
|
|
645
|
+
ctx.auth_mode,
|
|
646
|
+
ctx.mcp_config,
|
|
647
|
+
ctx.system_prompt,
|
|
648
|
+
model,
|
|
649
|
+
ctx.tools,
|
|
650
|
+
managed,
|
|
651
|
+
)?;
|
|
652
|
+
argv.push("--session-id".to_string());
|
|
653
|
+
argv.push(expected.clone());
|
|
654
|
+
argv.push("--resume".to_string());
|
|
655
|
+
argv.push(session_id.as_str().to_string());
|
|
656
|
+
argv.push("--fork-session".to_string());
|
|
657
|
+
Ok(CommandPlan {
|
|
658
|
+
argv,
|
|
659
|
+
expected_session_id: Some(SessionId::new(expected)),
|
|
660
|
+
provider_projects_root: projects_root,
|
|
661
|
+
managed_mcp_config: managed,
|
|
662
|
+
})
|
|
663
|
+
}
|
|
664
|
+
_ => self
|
|
665
|
+
.fork_with_context(
|
|
666
|
+
session_id,
|
|
667
|
+
ctx.auth_mode,
|
|
668
|
+
ctx.mcp_config,
|
|
669
|
+
ctx.system_prompt,
|
|
670
|
+
ctx.model,
|
|
671
|
+
ctx.tools,
|
|
672
|
+
)
|
|
673
|
+
.map(CommandPlan::argv_only),
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
447
677
|
fn mcp_config(&self, auth_mode: AuthMode) -> Result<McpConfig, ProviderError> {
|
|
448
678
|
let server = mcp_server_config(auth_mode);
|
|
449
679
|
Ok(McpConfig {
|
|
@@ -511,6 +741,101 @@ fn auth_mode_wire(auth_mode: AuthMode) -> &'static str {
|
|
|
511
741
|
}
|
|
512
742
|
}
|
|
513
743
|
|
|
744
|
+
fn claude_auth_hint(auth_mode: AuthMode) -> AuthHintStatus {
|
|
745
|
+
if auth_mode != AuthMode::Subscription {
|
|
746
|
+
return AuthHintStatus::MissingOrUnknown;
|
|
747
|
+
}
|
|
748
|
+
if !command_on_path("claude") {
|
|
749
|
+
return AuthHintStatus::Missing;
|
|
750
|
+
}
|
|
751
|
+
let output = match Command::new("claude").args(["auth", "status"]).output() {
|
|
752
|
+
Ok(output) => output,
|
|
753
|
+
Err(_) => return AuthHintStatus::MissingOrUnknown,
|
|
754
|
+
};
|
|
755
|
+
let text = if output.stdout.is_empty() {
|
|
756
|
+
String::from_utf8_lossy(&output.stderr).to_string()
|
|
757
|
+
} else {
|
|
758
|
+
String::from_utf8_lossy(&output.stdout).to_string()
|
|
759
|
+
};
|
|
760
|
+
let status = serde_json::from_str::<serde_json::Value>(text.trim()).unwrap_or_default();
|
|
761
|
+
if status.get("loggedIn").and_then(serde_json::Value::as_bool) == Some(true)
|
|
762
|
+
|| output.status.success()
|
|
763
|
+
{
|
|
764
|
+
AuthHintStatus::Present
|
|
765
|
+
} else {
|
|
766
|
+
AuthHintStatus::Missing
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
fn claude_context_model(ctx: ProviderCommandContext<'_>) -> Option<&str> {
|
|
771
|
+
ctx.profile_launch
|
|
772
|
+
.and_then(|profile| profile.command_overrides.model.as_deref())
|
|
773
|
+
.or(ctx.model)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
fn scan_session_candidates_once(
|
|
777
|
+
provider: Provider,
|
|
778
|
+
context: &CaptureSessionContext,
|
|
779
|
+
) -> Result<Vec<CapturedSessionCandidate>, ProviderError> {
|
|
780
|
+
let candidates = candidate_session_files(provider, context)?;
|
|
781
|
+
let mut out = Vec::new();
|
|
782
|
+
for candidate in candidates {
|
|
783
|
+
let path = candidate.path;
|
|
784
|
+
let Ok(text) = std::fs::read_to_string(&path) else {
|
|
785
|
+
continue;
|
|
786
|
+
};
|
|
787
|
+
let records = parse_session_records(&text);
|
|
788
|
+
if records.is_empty() {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if candidate.requires_cwd_match
|
|
792
|
+
&& !provider_home_records_match_spawn_cwd(&records, &context.spawn_cwd)
|
|
793
|
+
{
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
let session_id = records.iter().find_map(find_session_id);
|
|
797
|
+
if matches!(provider, Provider::Claude | Provider::ClaudeCode)
|
|
798
|
+
&& session_id.is_some()
|
|
799
|
+
&& !records.iter().any(has_cwd_field)
|
|
800
|
+
{
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
let captured_via = if session_id.is_some() {
|
|
804
|
+
CaptureVia::FsWatch
|
|
805
|
+
} else {
|
|
806
|
+
CaptureVia::FsMtimeFallback
|
|
807
|
+
};
|
|
808
|
+
let attribution_confidence = if session_id.is_some() {
|
|
809
|
+
Confidence::High
|
|
810
|
+
} else {
|
|
811
|
+
Confidence::Low
|
|
812
|
+
};
|
|
813
|
+
let positive_agent_id_match = candidate_text_has_team_agent_id(&text, context);
|
|
814
|
+
let agent_path_match = candidate_path_matches_agent_id(&path, context);
|
|
815
|
+
out.push(CapturedSessionCandidate {
|
|
816
|
+
captured: CapturedSession {
|
|
817
|
+
session_id: session_id.map(SessionId::new),
|
|
818
|
+
rollout_path: Some(RolloutPath::new(path)),
|
|
819
|
+
captured_via,
|
|
820
|
+
attribution_confidence,
|
|
821
|
+
spawn_cwd: context.spawn_cwd.clone(),
|
|
822
|
+
},
|
|
823
|
+
positive_agent_id_match,
|
|
824
|
+
agent_path_match,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
if let Some(expected) = context.expected_session_id.as_ref() {
|
|
828
|
+
out.sort_by_key(|candidate| {
|
|
829
|
+
candidate
|
|
830
|
+
.captured
|
|
831
|
+
.session_id
|
|
832
|
+
.as_ref()
|
|
833
|
+
.is_none_or(|session| session.as_str() != expected.as_str())
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
Ok(out)
|
|
837
|
+
}
|
|
838
|
+
|
|
514
839
|
fn command_on_path(name: &str) -> bool {
|
|
515
840
|
let Some(path) = std::env::var_os("PATH") else {
|
|
516
841
|
return false;
|
|
@@ -518,18 +843,58 @@ fn command_on_path(name: &str) -> bool {
|
|
|
518
843
|
std::env::split_paths(&path).any(|dir| dir.join(name).is_file())
|
|
519
844
|
}
|
|
520
845
|
|
|
521
|
-
|
|
846
|
+
struct SessionCandidate {
|
|
847
|
+
path: PathBuf,
|
|
848
|
+
requires_cwd_match: bool,
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
fn candidate_session_files(
|
|
852
|
+
provider: Provider,
|
|
853
|
+
context: &CaptureSessionContext,
|
|
854
|
+
) -> Result<Vec<SessionCandidate>, ProviderError> {
|
|
522
855
|
let mut out = Vec::new();
|
|
523
|
-
|
|
524
|
-
|
|
856
|
+
if let Some(root) = context.provider_projects_root.as_ref() {
|
|
857
|
+
collect_optional_candidate_files(root, &context.agent_id, &mut out)?;
|
|
858
|
+
}
|
|
859
|
+
collect_candidate_files(&context.spawn_cwd, &context.agent_id, 0, false, &mut out)?;
|
|
860
|
+
if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
|
|
861
|
+
match provider {
|
|
862
|
+
Provider::Codex => {
|
|
863
|
+
collect_optional_candidate_files(&home.join(".codex").join("sessions"), &context.agent_id, &mut out)?;
|
|
864
|
+
}
|
|
865
|
+
Provider::Claude | Provider::ClaudeCode => {
|
|
866
|
+
collect_optional_candidate_files(&home.join(".claude").join("sessions"), &context.agent_id, &mut out)?;
|
|
867
|
+
collect_optional_candidate_files(&home.join(".claude").join("projects"), &context.agent_id, &mut out)?;
|
|
868
|
+
}
|
|
869
|
+
Provider::GeminiCli | Provider::Fake => {}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
out.sort_by(|a, b| {
|
|
873
|
+
a.requires_cwd_match
|
|
874
|
+
.cmp(&b.requires_cwd_match)
|
|
875
|
+
.then_with(|| a.path.to_string_lossy().cmp(&b.path.to_string_lossy()))
|
|
876
|
+
});
|
|
877
|
+
out.dedup_by(|a, b| a.path == b.path && a.requires_cwd_match == b.requires_cwd_match);
|
|
525
878
|
Ok(out)
|
|
526
879
|
}
|
|
527
880
|
|
|
881
|
+
fn collect_optional_candidate_files(
|
|
882
|
+
dir: &Path,
|
|
883
|
+
agent_id: &str,
|
|
884
|
+
out: &mut Vec<SessionCandidate>,
|
|
885
|
+
) -> Result<(), ProviderError> {
|
|
886
|
+
if dir.exists() {
|
|
887
|
+
let _ = collect_candidate_files(dir, agent_id, 0, true, out);
|
|
888
|
+
}
|
|
889
|
+
Ok(())
|
|
890
|
+
}
|
|
891
|
+
|
|
528
892
|
fn collect_candidate_files(
|
|
529
893
|
dir: &Path,
|
|
530
894
|
agent_id: &str,
|
|
531
895
|
depth: usize,
|
|
532
|
-
|
|
896
|
+
requires_cwd_match: bool,
|
|
897
|
+
out: &mut Vec<SessionCandidate>,
|
|
533
898
|
) -> Result<(), ProviderError> {
|
|
534
899
|
if depth > 4 {
|
|
535
900
|
return Ok(());
|
|
@@ -545,9 +910,12 @@ fn collect_candidate_files(
|
|
|
545
910
|
};
|
|
546
911
|
let path = entry.path();
|
|
547
912
|
if path.is_dir() {
|
|
548
|
-
collect_candidate_files(&path, agent_id, depth.saturating_add(1), out)?;
|
|
913
|
+
collect_candidate_files(&path, agent_id, depth.saturating_add(1), requires_cwd_match, out)?;
|
|
549
914
|
} else if looks_like_session_file(&path, agent_id) {
|
|
550
|
-
out.push(
|
|
915
|
+
out.push(SessionCandidate {
|
|
916
|
+
path,
|
|
917
|
+
requires_cwd_match,
|
|
918
|
+
});
|
|
551
919
|
}
|
|
552
920
|
}
|
|
553
921
|
Ok(())
|
|
@@ -572,6 +940,77 @@ fn looks_like_session_file(path: &Path, agent_id: &str) -> bool {
|
|
|
572
940
|
|| (!agent_id.is_empty() && name.contains(agent_id))
|
|
573
941
|
}
|
|
574
942
|
|
|
943
|
+
fn parse_session_records(text: &str) -> Vec<serde_json::Value> {
|
|
944
|
+
match serde_json::from_str::<serde_json::Value>(text) {
|
|
945
|
+
Ok(serde_json::Value::Array(items)) => items,
|
|
946
|
+
Ok(value) => vec![value],
|
|
947
|
+
Err(_) => parse_jsonl_records(text),
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
fn provider_home_records_match_spawn_cwd(records: &[serde_json::Value], spawn_cwd: &Path) -> bool {
|
|
952
|
+
let cwd_values: Vec<String> = records.iter().filter_map(record_cwd).collect();
|
|
953
|
+
!cwd_values.is_empty()
|
|
954
|
+
&& cwd_values
|
|
955
|
+
.iter()
|
|
956
|
+
.any(|cwd| paths_equivalent(Path::new(cwd), spawn_cwd))
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
fn candidate_text_has_team_agent_id(text: &str, context: &CaptureSessionContext) -> bool {
|
|
960
|
+
let id = context.agent_id.as_str();
|
|
961
|
+
if id.is_empty() {
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
[
|
|
965
|
+
format!("\"TEAM_AGENT_ID\":\"{id}\""),
|
|
966
|
+
format!("\"TEAM_AGENT_ID\": \"{id}\""),
|
|
967
|
+
format!("TEAM_AGENT_ID={id}"),
|
|
968
|
+
format!("env.TEAM_AGENT_ID=\"{id}\""),
|
|
969
|
+
format!("env.TEAM_AGENT_ID=\\\"{id}\\\""),
|
|
970
|
+
format!("\"TEAM_AGENT_AGENT_ID\":\"{id}\""),
|
|
971
|
+
format!("\"TEAM_AGENT_AGENT_ID\": \"{id}\""),
|
|
972
|
+
format!("TEAM_AGENT_AGENT_ID={id}"),
|
|
973
|
+
]
|
|
974
|
+
.iter()
|
|
975
|
+
.any(|needle| text.contains(needle))
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
fn candidate_path_matches_agent_id(path: &Path, context: &CaptureSessionContext) -> bool {
|
|
979
|
+
let id = context.agent_id.as_str();
|
|
980
|
+
if id.is_empty() {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
let Some(name) = path.file_name().and_then(|value| value.to_str()) else {
|
|
984
|
+
return false;
|
|
985
|
+
};
|
|
986
|
+
let dashed = id.replace('_', "-");
|
|
987
|
+
name.contains(id) || name.contains(&dashed)
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
fn record_cwd(record: &serde_json::Value) -> Option<String> {
|
|
991
|
+
record
|
|
992
|
+
.get("cwd")
|
|
993
|
+
.and_then(serde_json::Value::as_str)
|
|
994
|
+
.or_else(|| {
|
|
995
|
+
record
|
|
996
|
+
.get("session_meta")
|
|
997
|
+
.and_then(|v| v.get("payload"))
|
|
998
|
+
.or_else(|| record.get("payload"))
|
|
999
|
+
.and_then(|v| v.get("cwd"))
|
|
1000
|
+
.and_then(serde_json::Value::as_str)
|
|
1001
|
+
})
|
|
1002
|
+
.map(ToString::to_string)
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
fn paths_equivalent(left: &Path, right: &Path) -> bool {
|
|
1006
|
+
if left == right {
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
1009
|
+
let left = std::fs::canonicalize(left).unwrap_or_else(|_| left.to_path_buf());
|
|
1010
|
+
let right = std::fs::canonicalize(right).unwrap_or_else(|_| right.to_path_buf());
|
|
1011
|
+
left == right || left.parent().is_some_and(|parent| parent == right)
|
|
1012
|
+
}
|
|
1013
|
+
|
|
575
1014
|
/// `true` iff any path component is `.team` (the Team Agent runtime/logs root) — used
|
|
576
1015
|
/// to gate session-file detection so `<workspace>/.team/logs/events.jsonl`,
|
|
577
1016
|
/// `.team/runtime/team.db`, etc. are NEVER mistaken for a provider transcript.
|
|
@@ -601,8 +1040,9 @@ fn claude_launch_command(
|
|
|
601
1040
|
mcp_config: Option<&McpConfig>,
|
|
602
1041
|
system_prompt: Option<&str>,
|
|
603
1042
|
model: Option<&str>,
|
|
1043
|
+
tools: &[&str],
|
|
604
1044
|
) -> Result<Vec<String>, ProviderError> {
|
|
605
|
-
let mut argv = claude_base_command(adapter, auth_mode, mcp_config, system_prompt, model)?;
|
|
1045
|
+
let mut argv = claude_base_command(adapter, auth_mode, mcp_config, system_prompt, model, tools, false)?;
|
|
606
1046
|
argv.push("--session-id".to_string());
|
|
607
1047
|
argv.push(next_session_token());
|
|
608
1048
|
Ok(argv)
|
|
@@ -614,12 +1054,16 @@ fn claude_base_command(
|
|
|
614
1054
|
mcp_config: Option<&McpConfig>,
|
|
615
1055
|
system_prompt: Option<&str>,
|
|
616
1056
|
model: Option<&str>,
|
|
1057
|
+
tools: &[&str],
|
|
1058
|
+
managed_mcp_config: bool,
|
|
617
1059
|
) -> Result<Vec<String>, ProviderError> {
|
|
618
|
-
let mut argv = vec![
|
|
619
|
-
|
|
620
|
-
"--
|
|
621
|
-
|
|
622
|
-
|
|
1060
|
+
let mut argv = vec!["claude".to_string()];
|
|
1061
|
+
if claude_dangerous_auto_approve(tools) {
|
|
1062
|
+
argv.push("--dangerously-skip-permissions".to_string());
|
|
1063
|
+
} else {
|
|
1064
|
+
argv.push("--permission-mode".to_string());
|
|
1065
|
+
argv.push("default".to_string());
|
|
1066
|
+
}
|
|
623
1067
|
if let Some(model) = model {
|
|
624
1068
|
argv.push("--model".to_string());
|
|
625
1069
|
argv.push(model.to_string());
|
|
@@ -628,7 +1072,11 @@ fn claude_base_command(
|
|
|
628
1072
|
argv.push("--append-system-prompt".to_string());
|
|
629
1073
|
argv.push(prompt.to_string());
|
|
630
1074
|
}
|
|
631
|
-
if
|
|
1075
|
+
if !managed_mcp_config
|
|
1076
|
+
&& (mcp_config.is_some()
|
|
1077
|
+
|| auth_mode == AuthMode::CompatibleApi
|
|
1078
|
+
|| system_prompt.is_some_and(prompt_needs_native_mcp))
|
|
1079
|
+
{
|
|
632
1080
|
let raw = if let Some(config) = mcp_config {
|
|
633
1081
|
serde_json::json!({"mcpServers": config.raw.clone()})
|
|
634
1082
|
} else {
|
|
@@ -637,10 +1085,10 @@ fn claude_base_command(
|
|
|
637
1085
|
argv.push("--mcp-config".to_string());
|
|
638
1086
|
argv.push(raw.to_string());
|
|
639
1087
|
argv.push("--strict-mcp-config".to_string());
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1088
|
+
}
|
|
1089
|
+
for tool in claude_disallowed_tools(tools) {
|
|
1090
|
+
argv.push("--disallowedTools".to_string());
|
|
1091
|
+
argv.push(tool.to_string());
|
|
644
1092
|
}
|
|
645
1093
|
Ok(argv)
|
|
646
1094
|
}
|
|
@@ -740,6 +1188,27 @@ fn codex_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
|
740
1188
|
tools.contains(&"dangerous_auto_approve")
|
|
741
1189
|
}
|
|
742
1190
|
|
|
1191
|
+
fn claude_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
1192
|
+
tools.contains(&"dangerous_auto_approve")
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
fn claude_disallowed_tools(tools: &[&str]) -> Vec<&'static str> {
|
|
1196
|
+
let mut disallowed = Vec::new();
|
|
1197
|
+
if !tools.contains(&"execute_bash") {
|
|
1198
|
+
disallowed.push("Bash");
|
|
1199
|
+
}
|
|
1200
|
+
if !tools.contains(&"fs_read") {
|
|
1201
|
+
disallowed.push("Read");
|
|
1202
|
+
}
|
|
1203
|
+
if !tools.contains(&"fs_write") {
|
|
1204
|
+
disallowed.extend(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
1205
|
+
}
|
|
1206
|
+
if !tools.contains(&"fs_list") {
|
|
1207
|
+
disallowed.extend(["Glob", "Grep"]);
|
|
1208
|
+
}
|
|
1209
|
+
disallowed
|
|
1210
|
+
}
|
|
1211
|
+
|
|
743
1212
|
fn codex_sandbox_mode(tools: &[&str]) -> &'static str {
|
|
744
1213
|
if tools.iter().any(|tool| matches!(*tool, "fs_write" | "execute_bash")) {
|
|
745
1214
|
"workspace-write"
|
|
@@ -790,12 +1259,44 @@ fn current_team_agent_command() -> String {
|
|
|
790
1259
|
}
|
|
791
1260
|
|
|
792
1261
|
fn has_cwd_field(record: &serde_json::Value) -> bool {
|
|
793
|
-
record
|
|
1262
|
+
record_cwd(record).is_some()
|
|
794
1263
|
}
|
|
795
1264
|
|
|
796
1265
|
fn next_session_token() -> String {
|
|
1266
|
+
use sha2::Digest;
|
|
1267
|
+
|
|
1268
|
+
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
|
1269
|
+
|
|
797
1270
|
let nanos = std::time::SystemTime::now()
|
|
798
1271
|
.duration_since(std::time::UNIX_EPOCH)
|
|
799
1272
|
.map_or(0, |d| d.as_nanos());
|
|
800
|
-
|
|
1273
|
+
let counter = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
1274
|
+
let mut hasher = sha2::Sha256::new();
|
|
1275
|
+
hasher.update(nanos.to_le_bytes());
|
|
1276
|
+
hasher.update(std::process::id().to_le_bytes());
|
|
1277
|
+
hasher.update(counter.to_le_bytes());
|
|
1278
|
+
let digest = hasher.finalize();
|
|
1279
|
+
let mut bytes = [0u8; 16];
|
|
1280
|
+
bytes.copy_from_slice(&digest[..16]);
|
|
1281
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
1282
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
1283
|
+
format!(
|
|
1284
|
+
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
|
|
1285
|
+
bytes[0],
|
|
1286
|
+
bytes[1],
|
|
1287
|
+
bytes[2],
|
|
1288
|
+
bytes[3],
|
|
1289
|
+
bytes[4],
|
|
1290
|
+
bytes[5],
|
|
1291
|
+
bytes[6],
|
|
1292
|
+
bytes[7],
|
|
1293
|
+
bytes[8],
|
|
1294
|
+
bytes[9],
|
|
1295
|
+
bytes[10],
|
|
1296
|
+
bytes[11],
|
|
1297
|
+
bytes[12],
|
|
1298
|
+
bytes[13],
|
|
1299
|
+
bytes[14],
|
|
1300
|
+
bytes[15],
|
|
1301
|
+
)
|
|
801
1302
|
}
|