@team-agent/installer 0.2.11 → 0.3.0
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 +744 -0
- package/Cargo.toml +34 -0
- package/crates/team-agent/Cargo.toml +33 -0
- package/crates/team-agent/src/cli/adapters.rs +1343 -0
- package/crates/team-agent/src/cli/diagnose.rs +554 -0
- package/crates/team-agent/src/cli/emit.rs +1077 -0
- package/crates/team-agent/src/cli/helpers.rs +88 -0
- package/crates/team-agent/src/cli/leader.rs +216 -0
- package/crates/team-agent/src/cli/mod.rs +1141 -0
- package/crates/team-agent/src/cli/profile.rs +306 -0
- package/crates/team-agent/src/cli/send.rs +215 -0
- package/crates/team-agent/src/cli/status.rs +179 -0
- package/crates/team-agent/src/cli/status_port.rs +502 -0
- package/crates/team-agent/src/cli/tests/base.rs +616 -0
- package/crates/team-agent/src/cli/tests/compile.rs +96 -0
- package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
- package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
- package/crates/team-agent/src/cli/tests/mod.rs +97 -0
- package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
- package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
- package/crates/team-agent/src/cli/types.rs +605 -0
- package/crates/team-agent/src/compiler/tests.rs +701 -0
- package/crates/team-agent/src/compiler.rs +489 -0
- package/crates/team-agent/src/coordinator/backoff.rs +153 -0
- package/crates/team-agent/src/coordinator/health.rs +436 -0
- package/crates/team-agent/src/coordinator/mod.rs +80 -0
- package/crates/team-agent/src/coordinator/orphan.rs +179 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
- package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
- package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
- package/crates/team-agent/src/coordinator/tick.rs +2032 -0
- package/crates/team-agent/src/coordinator/types.rs +584 -0
- package/crates/team-agent/src/db/migration.rs +716 -0
- package/crates/team-agent/src/db/mod.rs +23 -0
- package/crates/team-agent/src/db/schema.rs +378 -0
- package/crates/team-agent/src/event_log.rs +375 -0
- package/crates/team-agent/src/fake_worker.rs +253 -0
- package/crates/team-agent/src/leader/helpers.rs +190 -0
- package/crates/team-agent/src/leader/inject.rs +33 -0
- package/crates/team-agent/src/leader/lease.rs +1063 -0
- package/crates/team-agent/src/leader/mod.rs +99 -0
- package/crates/team-agent/src/leader/owner_bind.rs +292 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
- package/crates/team-agent/src/leader/rediscover.rs +1099 -0
- package/crates/team-agent/src/leader/start.rs +273 -0
- package/crates/team-agent/src/leader/takeover.rs +235 -0
- package/crates/team-agent/src/leader/tests/basics.rs +183 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +271 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
- package/crates/team-agent/src/leader/tests/mod.rs +125 -0
- package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
- package/crates/team-agent/src/leader/types.rs +487 -0
- package/crates/team-agent/src/lib.rs +85 -0
- package/crates/team-agent/src/lifecycle/display.rs +228 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
- package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
- package/crates/team-agent/src/lifecycle/mod.rs +62 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
- package/crates/team-agent/src/lifecycle/restart.rs +76 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests.rs +27 -0
- package/crates/team-agent/src/lifecycle/types.rs +685 -0
- package/crates/team-agent/src/main.rs +41 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
- package/crates/team-agent/src/mcp_server/mod.rs +183 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
- package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
- package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
- package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
- package/crates/team-agent/src/mcp_server/tests.rs +38 -0
- package/crates/team-agent/src/mcp_server/tools.rs +603 -0
- package/crates/team-agent/src/mcp_server/types.rs +421 -0
- package/crates/team-agent/src/mcp_server/wire.rs +388 -0
- package/crates/team-agent/src/message_store.rs +767 -0
- package/crates/team-agent/src/messaging/activity.rs +433 -0
- package/crates/team-agent/src/messaging/delivery.rs +542 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
- package/crates/team-agent/src/messaging/mod.rs +147 -0
- package/crates/team-agent/src/messaging/peers.rs +32 -0
- package/crates/team-agent/src/messaging/results.rs +537 -0
- package/crates/team-agent/src/messaging/scheduler.rs +344 -0
- package/crates/team-agent/src/messaging/selftest.rs +100 -0
- package/crates/team-agent/src/messaging/send.rs +582 -0
- package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
- package/crates/team-agent/src/messaging/trust.rs +192 -0
- package/crates/team-agent/src/messaging/types.rs +355 -0
- package/crates/team-agent/src/messaging/watchers.rs +591 -0
- package/crates/team-agent/src/model/enums.rs +311 -0
- package/crates/team-agent/src/model/errors.rs +17 -0
- package/crates/team-agent/src/model/ids.rs +155 -0
- package/crates/team-agent/src/model/mod.rs +22 -0
- package/crates/team-agent/src/model/paths.rs +228 -0
- package/crates/team-agent/src/model/permissions.rs +567 -0
- package/crates/team-agent/src/model/routing.rs +340 -0
- package/crates/team-agent/src/model/spec.rs +680 -0
- package/crates/team-agent/src/model/task_graph.rs +380 -0
- package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
- package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
- package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
- package/crates/team-agent/src/model/yaml/tests.rs +288 -0
- package/crates/team-agent/src/model/yaml.rs +800 -0
- package/crates/team-agent/src/packaging/install.rs +305 -0
- package/crates/team-agent/src/packaging/migrate.rs +30 -0
- package/crates/team-agent/src/packaging/mod.rs +82 -0
- package/crates/team-agent/src/packaging/repair.rs +24 -0
- package/crates/team-agent/src/packaging/tests.rs +829 -0
- package/crates/team-agent/src/packaging/types.rs +369 -0
- package/crates/team-agent/src/provider/adapter.rs +801 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
- package/crates/team-agent/src/provider/classify.rs +456 -0
- package/crates/team-agent/src/provider/faults.rs +136 -0
- package/crates/team-agent/src/provider/helpers.rs +41 -0
- package/crates/team-agent/src/provider/mod.rs +53 -0
- package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
- package/crates/team-agent/src/provider/tests/classify.rs +240 -0
- package/crates/team-agent/src/provider/tests/faults.rs +120 -0
- package/crates/team-agent/src/provider/tests/idle.rs +208 -0
- package/crates/team-agent/src/provider/tests/wire.rs +213 -0
- package/crates/team-agent/src/provider/tests.rs +31 -0
- package/crates/team-agent/src/provider/types.rs +424 -0
- package/crates/team-agent/src/state/identity.rs +656 -0
- package/crates/team-agent/src/state/mod.rs +58 -0
- package/crates/team-agent/src/state/owner_gate.rs +423 -0
- package/crates/team-agent/src/state/persist.rs +712 -0
- package/crates/team-agent/src/state/projection.rs +657 -0
- package/crates/team-agent/src/state/selector.rs +105 -0
- package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
- package/crates/team-agent/src/tmux_backend.rs +758 -0
- package/crates/team-agent/src/transport/test_support.rs +252 -0
- package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
- package/crates/team-agent/src/transport/tests/mod.rs +199 -0
- package/crates/team-agent/src/transport/tests/wire.rs +527 -0
- package/crates/team-agent/src/transport.rs +774 -0
- package/npm/install.mjs +90 -106
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -119
- package/src/team_agent/coordinator/lifecycle.py +0 -411
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -218
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -114
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -262
- package/src/team_agent/messaging/delivery.py +0 -504
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -503
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -91
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1243
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -144
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -693
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# Adding a provider idle/turn-state adapter
|
|
2
|
-
|
|
3
|
-
Gap 32 decides every node's idle/working/abnormal state from a deterministic
|
|
4
|
-
FILE FACT — the provider's own session-log/rollout turn-lifecycle records — never
|
|
5
|
-
from the pane screen. The predicate, abnormal track, and wake layers are
|
|
6
|
-
**provider-neutral and reused unchanged**. To support a brand-new CLI you fill the
|
|
7
|
-
small checklist below; you do not touch any neutral module.
|
|
8
|
-
|
|
9
|
-
## What you add (only two places)
|
|
10
|
-
|
|
11
|
-
1. `src/team_agent/provider_state/<provider>.py` — a thin reader that translates
|
|
12
|
-
that CLI's session records into normalized lifecycle facts.
|
|
13
|
-
2. one entry in `src/team_agent/provider_state/registry.py` — pure infra DATA.
|
|
14
|
-
|
|
15
|
-
Everything else (`idle_predicate.py`, `abnormal_track.py`, `wake.py`,
|
|
16
|
-
`idle_takeover.py`) is provider-neutral and must stay free of provider names
|
|
17
|
-
(there is a grep test, C6).
|
|
18
|
-
|
|
19
|
-
## The checklist
|
|
20
|
-
|
|
21
|
-
### 1. Session/rollout file location
|
|
22
|
-
- Where does this CLI write its per-session log? (root dir + path layout)
|
|
23
|
-
- How does the framework already learn each agent's path? (it is captured into
|
|
24
|
-
runtime state per agent as `rollout_path`; confirm yours lands there.)
|
|
25
|
-
- Record it under the registry entry `file_location`.
|
|
26
|
-
|
|
27
|
-
### 2. Turn-lifecycle event types (do the empirical capture FIRST)
|
|
28
|
-
Capture REAL records from a live session for each state and record the exact
|
|
29
|
-
record `type`/field. These become the contract fixtures (real-fixture-first):
|
|
30
|
-
- **turn-started / open turn** — the marker that a turn is in flight.
|
|
31
|
-
- **turn-complete** — the close that means idle.
|
|
32
|
-
- **interrupted** — user ESC / abort (idle_interrupted, idle + red note).
|
|
33
|
-
- **blocked / approval** — awaiting a human decision (blocked_on_human).
|
|
34
|
-
- **error / failed** — a structured terminal fault record.
|
|
35
|
-
Implement these as `extract_facts(records) -> (facts, diagnostics)` in your reader,
|
|
36
|
-
emitting `team_agent.provider_state.common` fact kinds: `TURN_OPEN`,
|
|
37
|
-
`TURN_COMPLETE`, `INTERRUPTED`, `FAILED`, `APPROVAL`, `ERROR`. Fault facts should
|
|
38
|
-
carry `signature`, `turn_id`, and `raw` (the original record). Filter out trailing
|
|
39
|
-
metadata/telemetry records so the verdict is the last LIFECYCLE fact, not the last
|
|
40
|
-
physical line.
|
|
41
|
-
|
|
42
|
-
Reference markers already implemented:
|
|
43
|
-
- Claude transcript: assistant `stop_reason==end_turn` (idle) / `==tool_use`
|
|
44
|
-
(working); user text `[Request interrupted by user]` (interrupted); user
|
|
45
|
-
`tool_result is_error==true` and system `subtype==api_error,level==error` (faults).
|
|
46
|
-
- Codex rollout: `event_msg payload.type==task_started|task_complete`;
|
|
47
|
-
`turn_aborted reason==interrupted`; app-server `turn.status==failed` and
|
|
48
|
-
`*/requestApproval`.
|
|
49
|
-
|
|
50
|
-
### 3. Black/white list seed entries
|
|
51
|
-
- `error_lists.whitelist` — record/string patterns that are benign → skip.
|
|
52
|
-
- `error_lists.blacklist` — known error signatures → notify (`api error`,
|
|
53
|
-
`rate limit`, `overloaded`, traceback/panic, provider `failed`, ...).
|
|
54
|
-
- Precedence is whitelist > blacklist > default-notify (catch-bias for structured
|
|
55
|
-
faults only). Lists are DATA — adding a pattern is one edit + one fixture.
|
|
56
|
-
|
|
57
|
-
### 4. Optional hook accelerator
|
|
58
|
-
- Does the CLI expose hooks that fire on turn boundaries (e.g. a `Stop`/`Notify`
|
|
59
|
-
program)? If so they can push a fact row to wake the watcher faster — but the
|
|
60
|
-
file fact remains the source of truth (the hook is validated against the file,
|
|
61
|
-
never the sole signal).
|
|
62
|
-
|
|
63
|
-
### 5. Process/identity facts for the liveness guard
|
|
64
|
-
- How to read the provider process identity (start-time / cmdline) so an open
|
|
65
|
-
turn whose process was replaced (PID reuse) classifies as `crashed_mid_turn`,
|
|
66
|
-
never eternal `working` (C4). `provider_state.common.process_is_live` already
|
|
67
|
-
implements the comparison given `{"expected": {...}, "current": {...}}`.
|
|
68
|
-
|
|
69
|
-
## Reused unchanged (do NOT modify per provider)
|
|
70
|
-
- `idle_predicate.evaluate_takeover_reminder` — all-idle + arm-after-delegation +
|
|
71
|
-
monotonic debounce + edge ack.
|
|
72
|
-
- `abnormal_track.process_abnormal_records` / `detect_whole_team_gone` — dedup,
|
|
73
|
-
catch-bias, coordinator-independent whole-team-gone.
|
|
74
|
-
- `wake` — file-change watch + mtime gate.
|
|
75
|
-
- `idle_takeover` — the public facade.
|
|
76
|
-
|
|
77
|
-
If you find yourself editing a neutral module to add a provider, stop — the fact
|
|
78
|
-
you need belongs in the reader or the registry entry instead.
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"""Provider turn-state readers behind one shared interface (Gap 32 §6).
|
|
2
|
-
|
|
3
|
-
``read_turn_state`` is the single entry the rest of the runtime uses; provider
|
|
4
|
-
dispatch happens here (and in registry data), so the neutral predicate /
|
|
5
|
-
abnormal / wake modules never name a provider.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import importlib
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
from team_agent.provider_state.registry import get_provider_registry
|
|
14
|
-
|
|
15
|
-
_READER_CACHE: dict[str, Any] = {}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def read_turn_state(
|
|
19
|
-
provider: str,
|
|
20
|
-
session_log_text: str,
|
|
21
|
-
*,
|
|
22
|
-
process: Any = None,
|
|
23
|
-
file_silence_seconds: float = 0,
|
|
24
|
-
registry: Any = None,
|
|
25
|
-
) -> dict[str, Any]:
|
|
26
|
-
"""Classify a node's turn state from its provider session-log text.
|
|
27
|
-
|
|
28
|
-
Returns the stable dict shape: state / turn_id / reason / source /
|
|
29
|
-
annotations / diagnostics. A missing/unknown provider or an unreadable
|
|
30
|
-
file fails safe to ``unknown`` (never idle, Gap 32 C5).
|
|
31
|
-
"""
|
|
32
|
-
_ = file_silence_seconds # open-turn beats silence (C14); silence never forces idle
|
|
33
|
-
reader = _reader_for(provider, registry)
|
|
34
|
-
if reader is None:
|
|
35
|
-
return {
|
|
36
|
-
"state": "unknown",
|
|
37
|
-
"turn_id": None,
|
|
38
|
-
"reason": "unknown_provider",
|
|
39
|
-
"source": "registry",
|
|
40
|
-
"annotations": [],
|
|
41
|
-
"diagnostics": [{"kind": "unknown_provider", "provider": provider}],
|
|
42
|
-
}
|
|
43
|
-
return reader.classify(session_log_text, process=process)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def read_fault_facts(provider: str, records: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
47
|
-
"""Extract normalized fault/approval facts from already-parsed provider
|
|
48
|
-
records, using the provider reader. The abnormal track consumes these
|
|
49
|
-
without naming a provider.
|
|
50
|
-
"""
|
|
51
|
-
reader = _reader_for(provider)
|
|
52
|
-
if reader is None or not hasattr(reader, "extract_facts"):
|
|
53
|
-
return []
|
|
54
|
-
facts, _diag = reader.extract_facts(records or [])
|
|
55
|
-
fault_kinds = {"error", "failed", "approval"}
|
|
56
|
-
out: list[dict[str, Any]] = []
|
|
57
|
-
for fact in facts:
|
|
58
|
-
if fact.get("kind") in fault_kinds:
|
|
59
|
-
enriched = dict(fact)
|
|
60
|
-
enriched.setdefault("provider", provider)
|
|
61
|
-
out.append(enriched)
|
|
62
|
-
return out
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _reader_for(provider: str, registry: Any = None) -> Any:
|
|
66
|
-
provider = _reader_provider(provider)
|
|
67
|
-
if provider in _READER_CACHE:
|
|
68
|
-
return _READER_CACHE[provider]
|
|
69
|
-
entry = None
|
|
70
|
-
if isinstance(registry, dict):
|
|
71
|
-
entry = registry.get(provider) if provider in registry else registry
|
|
72
|
-
if not isinstance(entry, dict) or "reader_module" not in entry:
|
|
73
|
-
entry = get_provider_registry(provider)
|
|
74
|
-
if not isinstance(entry, dict):
|
|
75
|
-
return None
|
|
76
|
-
module_name = entry.get("reader_module")
|
|
77
|
-
if not module_name:
|
|
78
|
-
return None
|
|
79
|
-
try:
|
|
80
|
-
module = importlib.import_module(module_name)
|
|
81
|
-
except ImportError:
|
|
82
|
-
return None
|
|
83
|
-
_READER_CACHE[provider] = module
|
|
84
|
-
return module
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def _reader_provider(provider: str) -> str:
|
|
88
|
-
return "claude" if provider == "claude_code" else provider
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
__all__ = ["read_turn_state", "read_fault_facts", "get_provider_registry"]
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""Claude transcript reader — the ONLY Claude-specific turn-state knowledge.
|
|
2
|
-
|
|
3
|
-
Translates Claude transcript JSONL records into normalized lifecycle facts.
|
|
4
|
-
Real markers (see turn-state-markers-evidence.md):
|
|
5
|
-
- assistant message.stop_reason == "tool_use" -> open turn (working)
|
|
6
|
-
- assistant message.stop_reason == "end_turn" -> turn complete (idle)
|
|
7
|
-
- user text == "[Request interrupted by user]" -> interrupted
|
|
8
|
-
- user tool_result is_error == true -> structured tool error
|
|
9
|
-
- system subtype == "api_error" and level=="error" -> provider api error
|
|
10
|
-
Trailing metadata records (stop_hook_summary / turn_duration / last-prompt /
|
|
11
|
-
ai-title / permission-mode / ...) are ignored for the turn verdict.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
from team_agent.provider_state import common
|
|
19
|
-
|
|
20
|
-
_INTERRUPT_TEXT = "[Request interrupted by user]"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def extract_facts(records: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
24
|
-
facts: list[dict[str, Any]] = []
|
|
25
|
-
diagnostics: list[dict[str, Any]] = []
|
|
26
|
-
for record in records:
|
|
27
|
-
rtype = record.get("type")
|
|
28
|
-
message = record.get("message")
|
|
29
|
-
if rtype == "assistant" and isinstance(message, dict):
|
|
30
|
-
stop_reason = message.get("stop_reason")
|
|
31
|
-
turn_id = record.get("requestId") or record.get("uuid")
|
|
32
|
-
if stop_reason == "end_turn":
|
|
33
|
-
facts.append({"kind": common.TURN_COMPLETE, "turn_id": turn_id, "reason": "end_turn"})
|
|
34
|
-
elif stop_reason == "tool_use":
|
|
35
|
-
facts.append({"kind": common.TURN_OPEN, "turn_id": turn_id, "reason": "tool_use"})
|
|
36
|
-
elif stop_reason == "stop_sequence":
|
|
37
|
-
facts.append({"kind": common.TURN_COMPLETE, "turn_id": turn_id, "reason": "stop_sequence"})
|
|
38
|
-
# other/missing stop_reason on assistant is treated as an open turn fragment
|
|
39
|
-
elif stop_reason is None and isinstance(message.get("content"), list):
|
|
40
|
-
facts.append({"kind": common.TURN_OPEN, "turn_id": turn_id, "reason": "assistant_in_flight"})
|
|
41
|
-
elif rtype == "user" and isinstance(message, dict):
|
|
42
|
-
content = message.get("content")
|
|
43
|
-
if _content_has_interrupt(content):
|
|
44
|
-
facts.append({"kind": common.INTERRUPTED, "turn_id": record.get("uuid"), "reason": "user_interrupt"})
|
|
45
|
-
elif _content_has_tool_error(content):
|
|
46
|
-
facts.append({
|
|
47
|
-
"kind": common.ERROR,
|
|
48
|
-
# the turn being retried/affected, stable across records (C8 dedup)
|
|
49
|
-
"turn_id": record.get("parentUuid") or record.get("uuid"),
|
|
50
|
-
"reason": "tool_result_is_error",
|
|
51
|
-
"signature": "tool_result_is_error",
|
|
52
|
-
"raw": record,
|
|
53
|
-
})
|
|
54
|
-
elif rtype == "system" and record.get("subtype") == "api_error" and record.get("level") == "error":
|
|
55
|
-
facts.append({
|
|
56
|
-
"kind": common.ERROR,
|
|
57
|
-
# api_error retries within a session dedup on (signature, session) (C8)
|
|
58
|
-
"turn_id": record.get("sessionId") or record.get("parentUuid") or record.get("uuid"),
|
|
59
|
-
"reason": "api_error",
|
|
60
|
-
"signature": "api_error",
|
|
61
|
-
"raw": record,
|
|
62
|
-
})
|
|
63
|
-
# everything else (metadata, snapshots, titles) is ignored for the verdict
|
|
64
|
-
return facts, diagnostics
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def classify(session_log_text: str, *, process: Any = None) -> dict[str, Any]:
|
|
68
|
-
return common.classify_with_reader(extract_facts, session_log_text, process=process)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _content_has_interrupt(content: Any) -> bool:
|
|
72
|
-
if not isinstance(content, list):
|
|
73
|
-
return False
|
|
74
|
-
for item in content:
|
|
75
|
-
if isinstance(item, dict) and item.get("type") == "text" and item.get("text") == _INTERRUPT_TEXT:
|
|
76
|
-
return True
|
|
77
|
-
return False
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _content_has_tool_error(content: Any) -> bool:
|
|
81
|
-
if not isinstance(content, list):
|
|
82
|
-
return False
|
|
83
|
-
for item in content:
|
|
84
|
-
if isinstance(item, dict) and item.get("type") == "tool_result" and item.get("is_error") is True:
|
|
85
|
-
return True
|
|
86
|
-
return False
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""Codex rollout reader — the ONLY Codex-specific turn-state knowledge.
|
|
2
|
-
|
|
3
|
-
Translates Codex rollout JSONL (and app-server jsonrpc) records into normalized
|
|
4
|
-
lifecycle facts. Real markers (see turn-state-markers-evidence.md):
|
|
5
|
-
- event_msg payload.type == "task_started" -> open turn (working)
|
|
6
|
-
- event_msg payload.type == "task_complete" -> turn complete (idle)
|
|
7
|
-
- event_msg payload.type == "turn_aborted" reason=="interrupted" -> interrupted
|
|
8
|
-
App-server schema-derived markers:
|
|
9
|
-
- method "turn/completed" params.turn.status == "failed" -> failed/error
|
|
10
|
-
- method ".../requestApproval" -> approval block
|
|
11
|
-
Telemetry (token_count, agent_message, patch_apply_end, ...) is not a close.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
from team_agent.provider_state import common
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def extract_facts(records: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
22
|
-
facts: list[dict[str, Any]] = []
|
|
23
|
-
diagnostics: list[dict[str, Any]] = []
|
|
24
|
-
for record in records:
|
|
25
|
-
rtype = record.get("type")
|
|
26
|
-
payload = record.get("payload") if isinstance(record.get("payload"), dict) else None
|
|
27
|
-
if rtype == "event_msg" and payload is not None:
|
|
28
|
-
ptype = payload.get("type")
|
|
29
|
-
turn_id = payload.get("turn_id")
|
|
30
|
-
if ptype == "task_started":
|
|
31
|
-
facts.append({"kind": common.TURN_OPEN, "turn_id": turn_id, "reason": "task_started"})
|
|
32
|
-
elif ptype == "task_complete":
|
|
33
|
-
facts.append({"kind": common.TURN_COMPLETE, "turn_id": turn_id, "reason": "task_complete"})
|
|
34
|
-
elif ptype == "turn_aborted" and payload.get("reason") == "interrupted":
|
|
35
|
-
facts.append({"kind": common.INTERRUPTED, "turn_id": turn_id, "reason": "interrupted"})
|
|
36
|
-
elif ptype == "turn_aborted":
|
|
37
|
-
facts.append({"kind": common.INTERRUPTED, "turn_id": turn_id, "reason": str(payload.get("reason") or "aborted")})
|
|
38
|
-
elif _is_app_server(record):
|
|
39
|
-
fact = _app_server_fact(record)
|
|
40
|
-
if fact is not None:
|
|
41
|
-
facts.append(fact)
|
|
42
|
-
# response_item (assistant/user messages), token_count, etc. are not verdicts
|
|
43
|
-
return facts, diagnostics
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def classify(session_log_text: str, *, process: Any = None) -> dict[str, Any]:
|
|
47
|
-
return common.classify_with_reader(extract_facts, session_log_text, process=process)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _is_app_server(record: dict[str, Any]) -> bool:
|
|
51
|
-
return record.get("jsonrpc") == "2.0" and isinstance(record.get("method"), str)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _app_server_fact(record: dict[str, Any]) -> dict[str, Any] | None:
|
|
55
|
-
method = str(record.get("method") or "")
|
|
56
|
-
params = record.get("params") if isinstance(record.get("params"), dict) else {}
|
|
57
|
-
if method == "turn/completed":
|
|
58
|
-
turn = params.get("turn") if isinstance(params.get("turn"), dict) else {}
|
|
59
|
-
status = turn.get("status")
|
|
60
|
-
turn_id = turn.get("id")
|
|
61
|
-
if status == "failed":
|
|
62
|
-
return {
|
|
63
|
-
"kind": common.FAILED,
|
|
64
|
-
"turn_id": turn_id,
|
|
65
|
-
"reason": "turn_failed",
|
|
66
|
-
"signature": "turn_failed",
|
|
67
|
-
"raw": record,
|
|
68
|
-
}
|
|
69
|
-
if status == "completed":
|
|
70
|
-
return {"kind": common.TURN_COMPLETE, "turn_id": turn_id, "reason": "completed"}
|
|
71
|
-
if status == "interrupted":
|
|
72
|
-
return {"kind": common.INTERRUPTED, "turn_id": turn_id, "reason": "interrupted"}
|
|
73
|
-
if status == "inProgress":
|
|
74
|
-
return {"kind": common.TURN_OPEN, "turn_id": turn_id, "reason": "in_progress"}
|
|
75
|
-
return None
|
|
76
|
-
if method.endswith("requestApproval"):
|
|
77
|
-
return {
|
|
78
|
-
"kind": common.APPROVAL,
|
|
79
|
-
"turn_id": params.get("turnId") or params.get("turn_id"),
|
|
80
|
-
"reason": "approval_required",
|
|
81
|
-
"signature": "approval_required",
|
|
82
|
-
"raw": record,
|
|
83
|
-
}
|
|
84
|
-
return None
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
"""Shared, provider-neutral plumbing for the turn-state readers.
|
|
2
|
-
|
|
3
|
-
The per-provider readers (claude.py, codex.py) only translate their own record
|
|
4
|
-
shapes into a normalized list of lifecycle facts; everything else — JSONL
|
|
5
|
-
tail parsing, metadata filtering wiring, the verdict decision, and the
|
|
6
|
-
process-identity liveness guard — lives here so it is written once.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
from typing import Any, Callable
|
|
13
|
-
|
|
14
|
-
# Normalized lifecycle fact kinds emitted by every reader.
|
|
15
|
-
TURN_OPEN = "turn_open"
|
|
16
|
-
TURN_COMPLETE = "turn_complete"
|
|
17
|
-
INTERRUPTED = "interrupted"
|
|
18
|
-
FAILED = "failed"
|
|
19
|
-
APPROVAL = "approval"
|
|
20
|
-
ERROR = "error" # non-closing structured error (e.g. transient api retry / tool is_error)
|
|
21
|
-
|
|
22
|
-
_CLOSING = {TURN_COMPLETE, INTERRUPTED, FAILED}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def parse_jsonl(text: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
26
|
-
"""Parse JSONL text into (records, parse_diagnostics).
|
|
27
|
-
|
|
28
|
-
Lines that are blank are skipped. Lines that are not valid JSON objects are
|
|
29
|
-
collected as diagnostics rather than raising — the caller decides whether a
|
|
30
|
-
populated diagnostics list with zero usable records means ``unknown``.
|
|
31
|
-
"""
|
|
32
|
-
records: list[dict[str, Any]] = []
|
|
33
|
-
diagnostics: list[dict[str, Any]] = []
|
|
34
|
-
for lineno, raw in enumerate(text.splitlines(), start=1):
|
|
35
|
-
line = raw.strip()
|
|
36
|
-
if not line:
|
|
37
|
-
continue
|
|
38
|
-
try:
|
|
39
|
-
obj = json.loads(line)
|
|
40
|
-
except (ValueError, TypeError):
|
|
41
|
-
diagnostics.append({"kind": "json_decode_error", "line": lineno})
|
|
42
|
-
continue
|
|
43
|
-
if not isinstance(obj, dict):
|
|
44
|
-
diagnostics.append({"kind": "non_object_record", "line": lineno})
|
|
45
|
-
continue
|
|
46
|
-
records.append(obj)
|
|
47
|
-
return records, diagnostics
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def decide_state(
|
|
51
|
-
facts: list[dict[str, Any]],
|
|
52
|
-
*,
|
|
53
|
-
process: Any = None,
|
|
54
|
-
parse_diagnostics: list[dict[str, Any]] | None = None,
|
|
55
|
-
had_records: bool,
|
|
56
|
-
extra_diagnostics: list[dict[str, Any]] | None = None,
|
|
57
|
-
) -> dict[str, Any]:
|
|
58
|
-
"""Turn a normalized fact stream into the public classify result.
|
|
59
|
-
|
|
60
|
-
Verdict = the LAST lifecycle fact, not the last physical record. An open
|
|
61
|
-
turn (a ``turn_open`` not yet closed) is a positive "still working" fact
|
|
62
|
-
that survives arbitrary file silence (Gap 32 C14); the only thing that can
|
|
63
|
-
demote it is a failed process-identity guard (Gap 32 C4).
|
|
64
|
-
"""
|
|
65
|
-
diagnostics = list(parse_diagnostics or []) + list(extra_diagnostics or [])
|
|
66
|
-
|
|
67
|
-
lifecycle = [f for f in facts if f.get("kind") in (_CLOSING | {TURN_OPEN, APPROVAL})]
|
|
68
|
-
if not lifecycle:
|
|
69
|
-
# No turn-lifecycle fact at all. If the input was unreadable/empty or a
|
|
70
|
-
# changed format with no recognizable records, fail safe to unknown (C5).
|
|
71
|
-
reason = "no_turn_lifecycle_fact"
|
|
72
|
-
if not had_records:
|
|
73
|
-
reason = "unreadable_or_empty"
|
|
74
|
-
elif diagnostics:
|
|
75
|
-
reason = "unrecognized_format"
|
|
76
|
-
return _result("unknown", None, reason, "session_file", [], diagnostics)
|
|
77
|
-
|
|
78
|
-
last = lifecycle[-1]
|
|
79
|
-
kind = last.get("kind")
|
|
80
|
-
turn_id = last.get("turn_id")
|
|
81
|
-
reason = str(last.get("reason") or kind)
|
|
82
|
-
|
|
83
|
-
if kind == TURN_COMPLETE:
|
|
84
|
-
return _result("idle", turn_id, reason or "turn_complete", "session_file", [], diagnostics)
|
|
85
|
-
if kind == INTERRUPTED:
|
|
86
|
-
return _result("idle_interrupted", turn_id, reason or "interrupted", "session_file", ["interrupted"], diagnostics)
|
|
87
|
-
if kind == FAILED:
|
|
88
|
-
return _result("abnormal", turn_id, reason or "turn_failed", "session_file", ["turn_failed"], diagnostics)
|
|
89
|
-
if kind == APPROVAL:
|
|
90
|
-
return _result("blocked_on_human", turn_id, reason or "approval_required", "session_file", ["awaiting_approval"], diagnostics)
|
|
91
|
-
|
|
92
|
-
# kind == TURN_OPEN with no later close → open turn. To declare "working" we
|
|
93
|
-
# must POSITIVELY confirm the recorded process is still alive (C4 fail-safe);
|
|
94
|
-
# missing/partial identity cannot be optimistically read as working.
|
|
95
|
-
verdict, live_reason, live_diag = process_liveness(process)
|
|
96
|
-
if live_diag:
|
|
97
|
-
diagnostics = diagnostics + [live_diag]
|
|
98
|
-
if verdict == "alive":
|
|
99
|
-
return _result("working", turn_id, "open_turn", "session_file", [], diagnostics)
|
|
100
|
-
if verdict == "dead":
|
|
101
|
-
return _result("abnormal", turn_id, "crashed_mid_turn", "process_guard", ["crashed_mid_turn", live_reason], diagnostics)
|
|
102
|
-
# unverifiable: cannot confirm alive → fail safe to unknown, never working.
|
|
103
|
-
return _result("unknown", turn_id, "process_identity_unverified", "process_guard", ["process_identity_unverified", live_reason], diagnostics)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
_STRONG_IDENTITY_FIELDS = ("start_time", "cmdline", "create_time")
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def process_liveness(process: Any) -> tuple[str, str, dict[str, Any] | None]:
|
|
110
|
-
"""Process-identity liveness guard (Gap 32 C4) — three-valued.
|
|
111
|
-
|
|
112
|
-
Returns (verdict, reason, diagnostic) where verdict is one of:
|
|
113
|
-
- ``"alive"`` — positively confirmed the same process is running
|
|
114
|
-
- ``"dead"`` — confirmed replaced/exited (identity mismatch or flag)
|
|
115
|
-
- ``"unverifiable"`` — identity missing/partial; CANNOT be read as working
|
|
116
|
-
|
|
117
|
-
Identity, not bare PID: aliveness must be affirmatively confirmed by a strong
|
|
118
|
-
identity field (start_time / cmdline / create_time) present and equal in BOTH
|
|
119
|
-
the recorded and the current snapshot. Missing/partial info is fail-safe
|
|
120
|
-
unverifiable, never optimistically "alive".
|
|
121
|
-
|
|
122
|
-
Accepted ``process`` shapes (any one):
|
|
123
|
-
- None / non-dict → unverifiable
|
|
124
|
-
- {"alive"|"running": bool} → explicit
|
|
125
|
-
- {"identity_match": bool} → explicit identity verdict
|
|
126
|
-
- {"expected"|"recorded": {...}, "current"|"observed": {...}}
|
|
127
|
-
"""
|
|
128
|
-
if process is None or not isinstance(process, dict):
|
|
129
|
-
return "unverifiable", "process_identity_missing", {"kind": "process_identity_unverified"}
|
|
130
|
-
if process.get("alive") is False or process.get("running") is False:
|
|
131
|
-
return "dead", "process_not_running", {"kind": "process_dead", "detail": "not_running"}
|
|
132
|
-
if process.get("identity_match") is False:
|
|
133
|
-
return "dead", "process_identity_mismatch", {"kind": "process_identity_mismatch"}
|
|
134
|
-
if process.get("alive") is True or process.get("running") is True or process.get("identity_match") is True:
|
|
135
|
-
return "alive", "process_alive", None
|
|
136
|
-
recorded = process.get("recorded") if isinstance(process.get("recorded"), dict) else process.get("expected")
|
|
137
|
-
current = process.get("current") if isinstance(process.get("current"), dict) else process.get("observed")
|
|
138
|
-
if not (isinstance(recorded, dict) and isinstance(current, dict)):
|
|
139
|
-
return "unverifiable", "process_identity_partial", {"kind": "process_identity_unverified"}
|
|
140
|
-
if current.get("alive") is False or current.get("running") is False:
|
|
141
|
-
return "dead", "process_not_running", {"kind": "process_dead", "detail": "current_not_running"}
|
|
142
|
-
# Any shared strong identity field that DIFFERS = confirmed replacement.
|
|
143
|
-
for key in _STRONG_IDENTITY_FIELDS:
|
|
144
|
-
if key in recorded and key in current and recorded.get(key) != current.get(key):
|
|
145
|
-
return "dead", f"process_identity_mismatch:{key}", {
|
|
146
|
-
"kind": "process_identity_mismatch",
|
|
147
|
-
"field": key,
|
|
148
|
-
"recorded": recorded.get(key),
|
|
149
|
-
"current": current.get(key),
|
|
150
|
-
}
|
|
151
|
-
# Require at least one strong identity field present+equal in BOTH, with no
|
|
152
|
-
# recorded strong field missing from current (else we cannot confirm).
|
|
153
|
-
recorded_strong = [k for k in _STRONG_IDENTITY_FIELDS if k in recorded]
|
|
154
|
-
confirmed = [k for k in recorded_strong if k in current and recorded.get(k) == current.get(k)]
|
|
155
|
-
missing = [k for k in recorded_strong if k not in current]
|
|
156
|
-
if confirmed and not missing:
|
|
157
|
-
return "alive", "process_identity_match", None
|
|
158
|
-
return "unverifiable", "process_identity_partial", {
|
|
159
|
-
"kind": "process_identity_unverified",
|
|
160
|
-
"recorded_strong": recorded_strong,
|
|
161
|
-
"confirmed": confirmed,
|
|
162
|
-
"missing": missing,
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def process_is_live(process: Any) -> tuple[bool, str, dict[str, Any] | None]:
|
|
167
|
-
"""Boolean wrapper used by conservative callers (e.g. whole-team-gone): a
|
|
168
|
-
process is treated as live unless it is CONFIRMED dead. Unverifiable counts
|
|
169
|
-
as live here so we never falsely declare the team gone."""
|
|
170
|
-
verdict, reason, diag = process_liveness(process)
|
|
171
|
-
return (verdict != "dead"), reason, diag
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _result(
|
|
175
|
-
state: str,
|
|
176
|
-
turn_id: str | None,
|
|
177
|
-
reason: str,
|
|
178
|
-
source: str,
|
|
179
|
-
annotations: list[str],
|
|
180
|
-
diagnostics: list[dict[str, Any]],
|
|
181
|
-
) -> dict[str, Any]:
|
|
182
|
-
return {
|
|
183
|
-
"state": state,
|
|
184
|
-
"turn_id": turn_id,
|
|
185
|
-
"reason": reason,
|
|
186
|
-
"source": source,
|
|
187
|
-
"annotations": list(annotations),
|
|
188
|
-
"diagnostics": list(diagnostics),
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def classify_with_reader(
|
|
193
|
-
extract_facts: Callable[[list[dict[str, Any]]], tuple[list[dict[str, Any]], list[dict[str, Any]]]],
|
|
194
|
-
session_log_text: str,
|
|
195
|
-
*,
|
|
196
|
-
process: Any = None,
|
|
197
|
-
) -> dict[str, Any]:
|
|
198
|
-
"""Run a provider reader's fact extractor through the shared pipeline."""
|
|
199
|
-
records, parse_diag = parse_jsonl(session_log_text or "")
|
|
200
|
-
facts, extra_diag = extract_facts(records)
|
|
201
|
-
return decide_state(
|
|
202
|
-
facts,
|
|
203
|
-
process=process,
|
|
204
|
-
parse_diagnostics=parse_diag,
|
|
205
|
-
had_records=bool(records),
|
|
206
|
-
extra_diagnostics=extra_diag,
|
|
207
|
-
)
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"""Per-CLI idle/turn-state registry — PURE INFRA DATA (Gap 32 C7).
|
|
2
|
-
|
|
3
|
-
This module is data only: session-file locations, turn-lifecycle marker
|
|
4
|
-
descriptions, and per-CLI error white/black lists. It carries no predicate,
|
|
5
|
-
abnormal, or wake logic. Adding a new provider is one entry here plus one
|
|
6
|
-
reader module under ``provider_state/``; the neutral layers never change.
|
|
7
|
-
|
|
8
|
-
The registry is shipped with the runtime as infra data — it is NOT
|
|
9
|
-
user-mandatory configuration and is never loaded from a workspace.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
# Each entry is consumed by the matching provider reader. The neutral
|
|
17
|
-
# idle_predicate / abnormal_track / wake modules never read provider names.
|
|
18
|
-
_PROVIDER_REGISTRY: dict[str, dict[str, Any]] = {
|
|
19
|
-
"claude": {
|
|
20
|
-
"kind": "claude",
|
|
21
|
-
"reader_module": "team_agent.provider_state.claude",
|
|
22
|
-
"source": "infra",
|
|
23
|
-
"file_location": {
|
|
24
|
-
"root": "~/.claude/projects",
|
|
25
|
-
"layout": "<cwd-slug>/<session_id>.jsonl",
|
|
26
|
-
"format": "transcript-jsonl",
|
|
27
|
-
},
|
|
28
|
-
"event_types": {
|
|
29
|
-
"turn_open": "assistant message.stop_reason == tool_use",
|
|
30
|
-
"turn_complete": "assistant message.stop_reason == end_turn",
|
|
31
|
-
"interrupted": "user text == [Request interrupted by user]",
|
|
32
|
-
"tool_error": "user tool_result is_error == true",
|
|
33
|
-
"api_error": "system subtype == api_error and level == error",
|
|
34
|
-
},
|
|
35
|
-
"metadata_ignore": [
|
|
36
|
-
"stop_hook_summary",
|
|
37
|
-
"turn_duration",
|
|
38
|
-
"last-prompt",
|
|
39
|
-
"ai-title",
|
|
40
|
-
"permission-mode",
|
|
41
|
-
"file-history-snapshot",
|
|
42
|
-
"queue-operation",
|
|
43
|
-
],
|
|
44
|
-
"error_whitelist": [],
|
|
45
|
-
"error_blacklist": [
|
|
46
|
-
"api_error",
|
|
47
|
-
"rate limit",
|
|
48
|
-
"overloaded",
|
|
49
|
-
"traceback",
|
|
50
|
-
"panic",
|
|
51
|
-
],
|
|
52
|
-
"error_lists": {
|
|
53
|
-
"whitelist": [],
|
|
54
|
-
"blacklist": ["api_error", "rate limit", "overloaded", "traceback", "panic"],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
"codex": {
|
|
58
|
-
"kind": "codex",
|
|
59
|
-
"reader_module": "team_agent.provider_state.codex",
|
|
60
|
-
"source": "infra",
|
|
61
|
-
"file_location": {
|
|
62
|
-
"root": "~/.codex/sessions",
|
|
63
|
-
"layout": "<YYYY>/<MM>/<DD>/rollout-<stamp>-<session_id>.jsonl",
|
|
64
|
-
"format": "rollout-jsonl",
|
|
65
|
-
},
|
|
66
|
-
"event_types": {
|
|
67
|
-
"turn_open": "event_msg payload.type == task_started",
|
|
68
|
-
"turn_complete": "event_msg payload.type == task_complete",
|
|
69
|
-
"interrupted": "event_msg payload.type == turn_aborted and reason == interrupted",
|
|
70
|
-
"failed": "app-server turn.status == failed",
|
|
71
|
-
"approval": "app-server method endswith requestApproval",
|
|
72
|
-
},
|
|
73
|
-
"metadata_ignore": [
|
|
74
|
-
"token_count",
|
|
75
|
-
"agent_message",
|
|
76
|
-
"context_compacted",
|
|
77
|
-
"mcp_tool_call_end",
|
|
78
|
-
"patch_apply_end",
|
|
79
|
-
"web_search_end",
|
|
80
|
-
"thread_goal_updated",
|
|
81
|
-
],
|
|
82
|
-
"error_whitelist": [],
|
|
83
|
-
"error_blacklist": [
|
|
84
|
-
"failed",
|
|
85
|
-
"api error",
|
|
86
|
-
"rate limit",
|
|
87
|
-
"overloaded",
|
|
88
|
-
"traceback",
|
|
89
|
-
"panic",
|
|
90
|
-
],
|
|
91
|
-
"error_lists": {
|
|
92
|
-
"whitelist": [],
|
|
93
|
-
"blacklist": ["failed", "api error", "rate limit", "overloaded", "traceback", "panic"],
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def get_provider_registry(provider: str | None = None) -> Any:
|
|
100
|
-
"""Return the infra registry.
|
|
101
|
-
|
|
102
|
-
With no argument, returns a copy of the whole per-CLI registry mapping.
|
|
103
|
-
With a provider name, returns that provider's entry (or ``None``).
|
|
104
|
-
"""
|
|
105
|
-
if provider is None:
|
|
106
|
-
return {name: _copy_entry(entry) for name, entry in _PROVIDER_REGISTRY.items()}
|
|
107
|
-
entry = _PROVIDER_REGISTRY.get(provider)
|
|
108
|
-
return _copy_entry(entry) if entry is not None else None
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def supported_providers() -> list[str]:
|
|
112
|
-
return sorted(_PROVIDER_REGISTRY)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _copy_entry(entry: dict[str, Any]) -> dict[str, Any]:
|
|
116
|
-
import copy
|
|
117
|
-
|
|
118
|
-
return copy.deepcopy(entry)
|