getcodexy 1.13.4__tar.gz → 2.0.0__tar.gz
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.
- {getcodexy-1.13.4 → getcodexy-2.0.0}/PKG-INFO +1 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/pyproject.toml +1 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/_version_data/uv.lock +1 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component-manifest.json +8 -12
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_capability_observation.py +14 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_capability_probe.py +21 -13
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_capability_probe_process.py +9 -9
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_cli.py +14 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_core_hooks.py +2 -11
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_health.py +53 -91
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_health_support.py +35 -7
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_hook_registration.py +2 -24
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_finish.py +13 -61
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_recovery.py +2 -2
- getcodexy-2.0.0/src/codexy_runtime_tools/component_mcp_capability.py +204 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_mcp_materialization.py +59 -89
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_registration_catalog.py +1 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_registration_files.py +3 -2
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_registration_health.py +108 -11
- getcodexy-2.0.0/src/codexy_runtime_tools/component_registration_lifecycle.py +107 -0
- getcodexy-2.0.0/src/codexy_runtime_tools/component_registration_orphans.py +93 -0
- getcodexy-2.0.0/src/codexy_runtime_tools/component_registration_projection.py +77 -0
- getcodexy-2.0.0/src/codexy_runtime_tools/component_registration_snapshot.py +104 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transaction_snapshot.py +8 -11
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/github_pre_session.py +5 -4
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/installer.py +0 -7
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/mcp_bootstrap.py +3 -2
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/package.py +2 -2
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/release_watcher_contract.py +1 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/runtime.py +2 -2
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/runtime_configuration.py +12 -9
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/updater.py +1 -1
- {getcodexy-1.13.4 → getcodexy-2.0.0}/uv.lock +1 -1
- getcodexy-1.13.4/src/codexy_runtime_tools/component_watcher_probe.py +0 -152
- {getcodexy-1.13.4 → getcodexy-2.0.0}/README.md +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/build_backend.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/__init__.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/activation_transaction.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/cache.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_hook_activation.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_hook_activation_expected.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_hook_activation_host.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_hook_activation_validation.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_inspection.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_inspection_host.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_inspection_types.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_integrity.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_interactive_probe.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_inventory_classification.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_inventory_records.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_admission.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_interlock.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_mcp.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_operation.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_preflight.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_support.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_lifecycle_terminal.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_manifest.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_manifest_dependencies.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_manifest_values.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_mcp_cache.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_mcp_probe.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_observed_inventory.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_resolver.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transaction_durability.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transaction_identity.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transaction_receipts.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transaction_state.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transition_journal.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transition_model.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transition_receipt.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_transition_rejections.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/contract.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/github_checks.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/identity.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/local_marketplace.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/marketplace_identity.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/marketplace_repin.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/monolith_baseline.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/monolith_classifier.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/monolith_migration.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/monolith_migration_host.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/monolith_migration_plan.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/monolith_migration_state.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/package_archive.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/plugin_resolution.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/pre_session.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/release_contract_validation.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/release_provenance.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/source.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/title_policy.py +0 -0
- {getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/version_lock.py +0 -0
|
@@ -5,7 +5,7 @@ backend-path = ["."]
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "getcodexy"
|
|
8
|
-
version = "
|
|
8
|
+
version = "2.0.0"
|
|
9
9
|
description = "A Codex harness for coordinating agents through planning, implementation, verification, and review."
|
|
10
10
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
{
|
|
30
30
|
"id": "core",
|
|
31
31
|
"plugin": "codexy",
|
|
32
|
-
"version": "
|
|
32
|
+
"version": "2.0.0",
|
|
33
33
|
"dependencies": [],
|
|
34
34
|
"asset": {
|
|
35
35
|
"pluginId": "codexy@codexy",
|
|
@@ -39,10 +39,6 @@
|
|
|
39
39
|
"assets/codexy-icon.png",
|
|
40
40
|
"skills/self-improvement/SKILL.md",
|
|
41
41
|
"skills/self-improvement/agents/openai.yaml",
|
|
42
|
-
".mcp.json",
|
|
43
|
-
"mcp/codexy_mcp_bootstrap.py",
|
|
44
|
-
"mcp/codexy-mcp-watcher.sh",
|
|
45
|
-
"mcp/codexy-mcp-watcher.cmd",
|
|
46
42
|
"skills/dreaming/scripts/resumable-context-capsule.sh",
|
|
47
43
|
"skills/dreaming/scripts/resumable-context-capsule.cmd",
|
|
48
44
|
"skills/dreaming/scripts/resumable_context_capsule.py",
|
|
@@ -63,7 +59,7 @@
|
|
|
63
59
|
{
|
|
64
60
|
"id": "github",
|
|
65
61
|
"plugin": "codexy-github",
|
|
66
|
-
"version": "
|
|
62
|
+
"version": "2.0.0",
|
|
67
63
|
"dependencies": [
|
|
68
64
|
"core"
|
|
69
65
|
],
|
|
@@ -79,7 +75,7 @@
|
|
|
79
75
|
{
|
|
80
76
|
"id": "devtools",
|
|
81
77
|
"plugin": "codexy-devtools",
|
|
82
|
-
"version": "
|
|
78
|
+
"version": "2.0.0",
|
|
83
79
|
"dependencies": [
|
|
84
80
|
"core"
|
|
85
81
|
],
|
|
@@ -97,27 +93,27 @@
|
|
|
97
93
|
"compatibleCombinations": [
|
|
98
94
|
{
|
|
99
95
|
"components": [],
|
|
100
|
-
"version": "
|
|
96
|
+
"version": "2.0.0"
|
|
101
97
|
},
|
|
102
98
|
{
|
|
103
99
|
"components": [
|
|
104
100
|
"core"
|
|
105
101
|
],
|
|
106
|
-
"version": "
|
|
102
|
+
"version": "2.0.0"
|
|
107
103
|
},
|
|
108
104
|
{
|
|
109
105
|
"components": [
|
|
110
106
|
"core",
|
|
111
107
|
"github"
|
|
112
108
|
],
|
|
113
|
-
"version": "
|
|
109
|
+
"version": "2.0.0"
|
|
114
110
|
},
|
|
115
111
|
{
|
|
116
112
|
"components": [
|
|
117
113
|
"core",
|
|
118
114
|
"devtools"
|
|
119
115
|
],
|
|
120
|
-
"version": "
|
|
116
|
+
"version": "2.0.0"
|
|
121
117
|
},
|
|
122
118
|
{
|
|
123
119
|
"components": [
|
|
@@ -125,7 +121,7 @@
|
|
|
125
121
|
"github",
|
|
126
122
|
"devtools"
|
|
127
123
|
],
|
|
128
|
-
"version": "
|
|
124
|
+
"version": "2.0.0"
|
|
129
125
|
}
|
|
130
126
|
]
|
|
131
127
|
}
|
{getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_capability_observation.py
RENAMED
|
@@ -11,17 +11,19 @@ _DIRECT_SCOPE = "plugin-subprocess"
|
|
|
11
11
|
_REGISTRATION_SOURCE = "getcodexy-registration-check"
|
|
12
12
|
_REGISTRATION_SCOPE = "current-doctor-invocation"
|
|
13
13
|
|
|
14
|
+
# Registration records configuration only; native specialist invocation remains unverified.
|
|
14
15
|
CAPABILITIES = {
|
|
15
16
|
"core": (
|
|
16
17
|
"hook:codexy-thread-delivery",
|
|
17
18
|
"specialist:codexy-architect",
|
|
19
|
+
"specialist:codexy-advisor",
|
|
18
20
|
"specialist:codexy-auditor",
|
|
19
21
|
"specialist:codexy-cartographer",
|
|
20
22
|
"specialist:codexy-inspector",
|
|
21
23
|
"specialist:codexy-sentinel",
|
|
22
24
|
"specialist:codexy-shipwright",
|
|
23
25
|
"specialist:codexy-warden",
|
|
24
|
-
|
|
26
|
+
# Legacy registration remains visible without implying a current Watcher process.
|
|
25
27
|
"mcp:watcher",
|
|
26
28
|
),
|
|
27
29
|
"github": ("hook:codexy-github-workflow-context", "specialist:codexy-weaver"),
|
|
@@ -45,7 +47,18 @@ def component_observations(
|
|
|
45
47
|
direct = probe.get("_capability_probes")
|
|
46
48
|
if not isinstance(direct, dict):
|
|
47
49
|
direct = {}
|
|
50
|
+
contract = probe.get("_capability_contract")
|
|
51
|
+
if (
|
|
52
|
+
component == "core"
|
|
53
|
+
and isinstance(contract, dict)
|
|
54
|
+
and contract.get("mcp:watcher") is False
|
|
55
|
+
):
|
|
56
|
+
# A validated no-MCP core has no configured Watcher capability to report.
|
|
57
|
+
observations.pop("mcp:watcher", None)
|
|
48
58
|
for name in observations:
|
|
59
|
+
if name == "mcp:watcher":
|
|
60
|
+
# Current getcodexy never launches legacy Watcher MCP, so execution stays unknown.
|
|
61
|
+
continue
|
|
49
62
|
details = direct.get(name)
|
|
50
63
|
if isinstance(details, dict):
|
|
51
64
|
observations[name] = _entry(
|
|
@@ -13,7 +13,7 @@ from .component_capability_probe_process import (
|
|
|
13
13
|
_probe_diagnostics,
|
|
14
14
|
_run,
|
|
15
15
|
)
|
|
16
|
-
from .
|
|
16
|
+
from .component_mcp_materialization import selected_mcp_spec, valid_component_mcp
|
|
17
17
|
from .version_lock import default_package_version
|
|
18
18
|
|
|
19
19
|
|
|
@@ -57,7 +57,7 @@ def _request(method, identifier=None, params=None):
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def probe_component(component, plugin, record):
|
|
60
|
-
"""Probe one installed component
|
|
60
|
+
"""Probe one installed component without starting legacy core MCP servers."""
|
|
61
61
|
base = _base(record)
|
|
62
62
|
if plugin is None:
|
|
63
63
|
return base
|
|
@@ -68,17 +68,25 @@ def probe_component(component, plugin, record):
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def _probe_core(plugin, base):
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
# Preserve legacy registration visibility while the current doctor stays read-only.
|
|
72
|
+
watcher_declared = _core_declares_watcher(plugin)
|
|
73
|
+
if watcher_declared is not None:
|
|
74
|
+
base["_capability_contract"] = {"mcp:watcher": watcher_declared}
|
|
75
|
+
return _probe_hook("core", plugin, base)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _core_declares_watcher(plugin):
|
|
79
|
+
"""Read legacy Watcher registration without launching its configured runner."""
|
|
80
|
+
try:
|
|
81
|
+
manifest = json.loads(
|
|
82
|
+
(plugin / ".codex-plugin/plugin.json").read_text(encoding="utf-8")
|
|
83
|
+
)
|
|
84
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
85
|
+
return None
|
|
86
|
+
version = manifest.get("version") if isinstance(manifest, dict) else None
|
|
87
|
+
if not isinstance(version, str) or not valid_component_mcp(plugin, "core", version):
|
|
88
|
+
return None
|
|
89
|
+
return "watcher" in selected_mcp_spec(plugin, "core").servers
|
|
82
90
|
|
|
83
91
|
|
|
84
92
|
def _probe_hook(component, plugin, base):
|
{getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_capability_probe_process.py
RENAMED
|
@@ -97,6 +97,7 @@ def _run_windows(argv, cwd, input_text, env, deadline):
|
|
|
97
97
|
)
|
|
98
98
|
cleanup_deadline = deadline
|
|
99
99
|
abort_pipes = False
|
|
100
|
+
threads_stopped = True
|
|
100
101
|
try:
|
|
101
102
|
remaining = max(0.0, deadline - perf_counter())
|
|
102
103
|
if not remaining:
|
|
@@ -107,16 +108,17 @@ def _run_windows(argv, cwd, input_text, env, deadline):
|
|
|
107
108
|
abort_pipes = True
|
|
108
109
|
cleanup_deadline = perf_counter() + _WINDOWS_CLEANUP_TIMEOUT
|
|
109
110
|
_terminate_process_tree(process, cleanup_deadline)
|
|
110
|
-
_cancel_windows_pipe_threads(process, cleanup_deadline)
|
|
111
|
+
threads_stopped = _cancel_windows_pipe_threads(process, cleanup_deadline)
|
|
111
112
|
remaining = max(0.0, cleanup_deadline - perf_counter())
|
|
112
113
|
if remaining:
|
|
113
114
|
try:
|
|
114
|
-
process.communicate(timeout=remaining)
|
|
115
|
+
_ = process.communicate(timeout=remaining)
|
|
116
|
+
threads_stopped = True
|
|
115
117
|
except (OSError, ValueError, subprocess.TimeoutExpired):
|
|
116
118
|
pass
|
|
117
119
|
raise
|
|
118
120
|
finally:
|
|
119
|
-
_close_process_pipes(process,
|
|
121
|
+
_close_process_pipes(process, abort_pipes, threads_stopped)
|
|
120
122
|
_wait_for_exit(process, cleanup_deadline)
|
|
121
123
|
|
|
122
124
|
|
|
@@ -145,10 +147,8 @@ def _terminate_process_tree(process, deadline):
|
|
|
145
147
|
pass
|
|
146
148
|
|
|
147
149
|
|
|
148
|
-
def _close_process_pipes(process, abort=False,
|
|
149
|
-
|
|
150
|
-
if abort:
|
|
151
|
-
threads_stopped = _cancel_windows_pipe_threads(process, deadline)
|
|
150
|
+
def _close_process_pipes(process, abort=False, threads_stopped: bool = False):
|
|
151
|
+
# Timeout handling owns cancellation; repeating it here can outlive the deadline.
|
|
152
152
|
for attribute in ("stdin", "stdout", "stderr"):
|
|
153
153
|
stream = getattr(process, attribute, None)
|
|
154
154
|
if stream is None:
|
|
@@ -177,7 +177,7 @@ def _abort_process_pipe(process, attribute, stream):
|
|
|
177
177
|
setattr(process, attribute, None)
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
def _cancel_windows_pipe_threads(process, deadline):
|
|
180
|
+
def _cancel_windows_pipe_threads(process, deadline) -> bool:
|
|
181
181
|
threads = [
|
|
182
182
|
getattr(process, attribute, None)
|
|
183
183
|
for attribute in ("stdin_thread", "stdout_thread", "stderr_thread")
|
|
@@ -186,7 +186,7 @@ def _cancel_windows_pipe_threads(process, deadline):
|
|
|
186
186
|
if not threads:
|
|
187
187
|
return False
|
|
188
188
|
for thread in threads:
|
|
189
|
-
if thread.is_alive():
|
|
189
|
+
if thread.is_alive() and deadline > perf_counter():
|
|
190
190
|
_cancel_synchronous_io(thread)
|
|
191
191
|
for thread in threads:
|
|
192
192
|
if not thread.is_alive():
|
|
@@ -90,7 +90,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
90
90
|
unhealthy = arguments.command in {"status", "doctor"} and bool(
|
|
91
91
|
receipt.get("errors")
|
|
92
92
|
)
|
|
93
|
-
|
|
93
|
+
# Doctor health findings stay outside the closed lifecycle-domain error codes.
|
|
94
|
+
orphaned_registration = arguments.command == "doctor" and any(
|
|
95
|
+
isinstance(entry, dict)
|
|
96
|
+
and entry.get("reason_code")
|
|
97
|
+
in {"orphaned-managed-registration", "registration-inspection-unavailable"}
|
|
98
|
+
for entry in receipt.get("component_health", [])
|
|
99
|
+
)
|
|
100
|
+
return (
|
|
101
|
+
0
|
|
102
|
+
if receipt["outcome"] == "completed"
|
|
103
|
+
and not unhealthy
|
|
104
|
+
and not orphaned_registration
|
|
105
|
+
else 2
|
|
106
|
+
)
|
|
94
107
|
|
|
95
108
|
|
|
96
109
|
def _record_capability_failures(receipt: dict[str, object]) -> None:
|
|
@@ -7,15 +7,10 @@ COMMAND_HOOKS = (
|
|
|
7
7
|
(_TOOL_PREFIX + "create_thread$", "codexy-child-thread-creation"),
|
|
8
8
|
(r"^(?:(?:agents|multi_agent_v1)__)?spawn_agent$", "codexy-subagent-ownership"),
|
|
9
9
|
)
|
|
10
|
-
|
|
11
|
-
LIFECYCLE_HOOKS = (WATCHER_WAIT_HOOK,)
|
|
12
|
-
# Observe only completed sender calls; failed sends cannot release recipient waits.
|
|
13
|
-
POST_TOOL_HOOKS = (
|
|
14
|
-
(r"^mcp__codex_app__send_message_to_thread$", "codexy-watcher-interrupt"),
|
|
15
|
-
)
|
|
10
|
+
# The installed core package carries only its retained routing and ownership launchers.
|
|
16
11
|
LAUNCHERS = tuple(
|
|
17
12
|
f"hooks/{stem}.{extension}"
|
|
18
|
-
for _, stem in
|
|
13
|
+
for _, stem in COMMAND_HOOKS
|
|
19
14
|
for extension in ("sh", "cmd")
|
|
20
15
|
)
|
|
21
16
|
# Windows invokes the event script with `py.exe -3 -I -B`, bypassing the POSIX
|
|
@@ -33,8 +28,4 @@ DEPENDENCIES = (
|
|
|
33
28
|
"hooks/codexy_policy/envelope.py",
|
|
34
29
|
"hooks/codexy_policy/timing.py",
|
|
35
30
|
"hooks/codexy_policy/windows_timing.py",
|
|
36
|
-
"hooks/codexy-watcher-interrupt.sh",
|
|
37
|
-
"hooks/codexy-watcher-interrupt.cmd",
|
|
38
|
-
"hooks/codexy_watcher_interrupt.py",
|
|
39
|
-
"hooks/codexy_watcher_interrupt_events.py",
|
|
40
31
|
)
|
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
5
|
from . import component_capability_probe as _probe
|
|
9
6
|
from .component_capability_observation import component_observations
|
|
10
7
|
from .component_hook_activation import ACTIVATION_STATES
|
|
11
|
-
from .component_mcp_materialization import
|
|
8
|
+
from .component_mcp_materialization import (
|
|
9
|
+
MCP_COMPONENTS,
|
|
10
|
+
mcp_spec_for_plugin,
|
|
11
|
+
valid_component_mcp_cache,
|
|
12
|
+
)
|
|
12
13
|
from .component_health_support import (
|
|
13
14
|
_authority_valid,
|
|
14
15
|
_health_plugin,
|
|
15
16
|
_legacy_state,
|
|
16
17
|
_observed,
|
|
18
|
+
_plugin_root,
|
|
19
|
+
_selected_registration_valid,
|
|
17
20
|
manifest_is_valid,
|
|
18
21
|
record_version,
|
|
19
22
|
version_relation,
|
|
20
23
|
)
|
|
21
24
|
from .component_manifest import ComponentManifest
|
|
22
|
-
from .
|
|
23
|
-
registration_role as _registration_role,
|
|
24
|
-
valid_registration,
|
|
25
|
-
)
|
|
25
|
+
from .component_registration_orphans import include_orphaned_registrations
|
|
26
26
|
from .updater import compare_managed_files
|
|
27
27
|
|
|
28
28
|
FAILURES = _probe.FAILURES
|
|
@@ -51,11 +51,12 @@ def health(
|
|
|
51
51
|
activation,
|
|
52
52
|
codex_home,
|
|
53
53
|
)
|
|
54
|
-
|
|
54
|
+
entries = [
|
|
55
55
|
_component_health(context, component)
|
|
56
56
|
for component in manifest.component_ids
|
|
57
57
|
if component in expected
|
|
58
58
|
]
|
|
59
|
+
return include_orphaned_registrations(entries, manifest, actual, codex_home)
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def _component_health(context, component):
|
|
@@ -64,6 +65,17 @@ def _component_health(context, component):
|
|
|
64
65
|
)
|
|
65
66
|
record = records.get(component)
|
|
66
67
|
installed = component in actual
|
|
68
|
+
expected_mcp_spec = None
|
|
69
|
+
if component in MCP_COMPONENTS:
|
|
70
|
+
source_plugin = _plugin_root(record)
|
|
71
|
+
if source_plugin is not None:
|
|
72
|
+
try:
|
|
73
|
+
# Check package shape separately; authority remains a later health check.
|
|
74
|
+
expected_mcp_spec = mcp_spec_for_plugin(
|
|
75
|
+
source_plugin, component, record_version(record)
|
|
76
|
+
)
|
|
77
|
+
except (OSError, RuntimeError, ValueError):
|
|
78
|
+
expected_mcp_spec = None
|
|
67
79
|
plugin = _health_plugin(manifest, component, record, codex_home)
|
|
68
80
|
registration = (
|
|
69
81
|
compare_managed_files(plugin, codex_home, component)
|
|
@@ -76,7 +88,9 @@ def _component_health(context, component):
|
|
|
76
88
|
and manifest_is_valid(
|
|
77
89
|
plugin, manifest.component(component).plugin, record_version(record)
|
|
78
90
|
)
|
|
79
|
-
and
|
|
91
|
+
and _selected_registration_valid(
|
|
92
|
+
plugin, component, record_version(record), expected_mcp_spec
|
|
93
|
+
)
|
|
80
94
|
and (
|
|
81
95
|
not registration
|
|
82
96
|
or not registration["observed"]
|
|
@@ -84,11 +98,26 @@ def _component_health(context, component):
|
|
|
84
98
|
)
|
|
85
99
|
and (
|
|
86
100
|
component not in MCP_COMPONENTS
|
|
87
|
-
or
|
|
101
|
+
or (
|
|
102
|
+
expected_mcp_spec is not None
|
|
103
|
+
and valid_component_mcp_cache(
|
|
104
|
+
codex_home,
|
|
105
|
+
component,
|
|
106
|
+
manifest.version,
|
|
107
|
+
expected_spec=expected_mcp_spec,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
88
110
|
)
|
|
89
111
|
)
|
|
90
112
|
state = _legacy_state(
|
|
91
|
-
manifest,
|
|
113
|
+
manifest,
|
|
114
|
+
component,
|
|
115
|
+
actual,
|
|
116
|
+
records,
|
|
117
|
+
admission_error,
|
|
118
|
+
host_error,
|
|
119
|
+
codex_home,
|
|
120
|
+
expected_mcp_spec,
|
|
92
121
|
)
|
|
93
122
|
registration_state = registration["state"] if registration else "exact"
|
|
94
123
|
if state in {"healthy", "stale"} and registration_state != "exact":
|
|
@@ -109,7 +138,18 @@ def _component_health(context, component):
|
|
|
109
138
|
restart_required=False,
|
|
110
139
|
observed={
|
|
111
140
|
**_observed(record),
|
|
112
|
-
|
|
141
|
+
# Keep a no-MCP core accurate when activation prevents its live probe.
|
|
142
|
+
"capabilities": component_observations(
|
|
143
|
+
component,
|
|
144
|
+
configured,
|
|
145
|
+
{
|
|
146
|
+
"_capability_contract": {
|
|
147
|
+
"mcp:watcher": "watcher" in expected_mcp_spec.servers
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if component == "core" and expected_mcp_spec is not None
|
|
151
|
+
else None,
|
|
152
|
+
),
|
|
113
153
|
"registration": registration,
|
|
114
154
|
},
|
|
115
155
|
)
|
|
@@ -170,81 +210,3 @@ def _mark(result: dict[str, object], stage: str, reason: str) -> dict[str, objec
|
|
|
170
210
|
):
|
|
171
211
|
result["state"] = "incompatible"
|
|
172
212
|
return result
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def _registration_roles(home, root, marker, expected, read_regular, max_bytes):
|
|
176
|
-
roles, managed, unmanaged = [], [], [] # Track expected and marker-owned roles.
|
|
177
|
-
for name, value in expected.items():
|
|
178
|
-
roles.append(
|
|
179
|
-
_registered_role(home, root, name, value, marker, read_regular, max_bytes)
|
|
180
|
-
)
|
|
181
|
-
for entry in os.scandir(root):
|
|
182
|
-
if entry.name in expected or not entry.name.endswith(".toml"):
|
|
183
|
-
continue
|
|
184
|
-
try:
|
|
185
|
-
current = (
|
|
186
|
-
read_regular(home, Path(entry.path).relative_to(home), max_bytes)
|
|
187
|
-
.decode()
|
|
188
|
-
.replace("\r\n", "\n")
|
|
189
|
-
.replace("\r", "\n")
|
|
190
|
-
)
|
|
191
|
-
except (OSError, UnicodeDecodeError, ValueError):
|
|
192
|
-
unmanaged.append(entry.name)
|
|
193
|
-
continue
|
|
194
|
-
(managed if current.startswith(marker) else unmanaged).append(entry.name)
|
|
195
|
-
roles.extend(
|
|
196
|
-
_registration_role(
|
|
197
|
-
name, "stale", "managed role is not declared by the current catalog"
|
|
198
|
-
)
|
|
199
|
-
for name in managed
|
|
200
|
-
)
|
|
201
|
-
states = {item["state"] for item in roles}
|
|
202
|
-
state = next(
|
|
203
|
-
(
|
|
204
|
-
candidate
|
|
205
|
-
for candidate in ("unmanaged-conflict", "stale", "missing")
|
|
206
|
-
if candidate in states or candidate == "unmanaged-conflict" and unmanaged
|
|
207
|
-
),
|
|
208
|
-
"exact",
|
|
209
|
-
)
|
|
210
|
-
return roles, tuple(sorted(managed)), tuple(sorted(unmanaged)), state
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def _registered_role(
|
|
214
|
-
home: Path,
|
|
215
|
-
root: Path,
|
|
216
|
-
name: str,
|
|
217
|
-
expected: bytes,
|
|
218
|
-
marker: str,
|
|
219
|
-
read_regular,
|
|
220
|
-
max_bytes: int,
|
|
221
|
-
) -> dict[str, object]:
|
|
222
|
-
try: # Inspect the marker-owned role file as a bounded regular input.
|
|
223
|
-
current = (
|
|
224
|
-
read_regular(home, root.relative_to(home) / name, max_bytes)
|
|
225
|
-
.decode()
|
|
226
|
-
.replace("\r\n", "\n")
|
|
227
|
-
.replace("\r", "\n")
|
|
228
|
-
)
|
|
229
|
-
except FileNotFoundError:
|
|
230
|
-
return _registration_role(name, "missing", "managed role file is missing")
|
|
231
|
-
except (OSError, UnicodeDecodeError, ValueError):
|
|
232
|
-
return _registration_role(
|
|
233
|
-
name,
|
|
234
|
-
"unmanaged-conflict",
|
|
235
|
-
"managed role file is unreadable or not a regular file",
|
|
236
|
-
True,
|
|
237
|
-
)
|
|
238
|
-
if not current.startswith(marker):
|
|
239
|
-
return _registration_role(
|
|
240
|
-
name,
|
|
241
|
-
"unmanaged-conflict",
|
|
242
|
-
"existing role file is not marker-owned by Codexy",
|
|
243
|
-
True,
|
|
244
|
-
)
|
|
245
|
-
exact = current.encode() == expected
|
|
246
|
-
return _registration_role(
|
|
247
|
-
name,
|
|
248
|
-
"exact" if exact else "stale",
|
|
249
|
-
None if exact else "registered role differs from the package",
|
|
250
|
-
)
|
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from .component_mcp_materialization import (
|
|
8
8
|
MCP_COMPONENTS,
|
|
9
9
|
component_cache_plugin,
|
|
10
|
-
|
|
10
|
+
McpSpec,
|
|
11
11
|
)
|
|
12
12
|
from .component_manifest import ComponentManifest
|
|
13
13
|
from .component_registration_health import valid_registration
|
|
@@ -19,14 +19,25 @@ SURFACE_PATHS = {
|
|
|
19
19
|
"agents/catalog.toml",
|
|
20
20
|
"hooks/hooks.json",
|
|
21
21
|
"skills/wiki/SKILL.md",
|
|
22
|
-
*(path.as_posix() for path in mcp_spec("core").surface_paths),
|
|
23
22
|
),
|
|
24
23
|
"github": ("agents/catalog.toml", "hooks/hooks.json"),
|
|
25
|
-
"devtools":
|
|
24
|
+
"devtools": (),
|
|
26
25
|
}
|
|
27
26
|
AUTHORITY_KEYS = ("authority", "artifact_authority", "artifactAuthority")
|
|
28
27
|
|
|
29
28
|
|
|
29
|
+
def _selected_registration_valid(plugin, component, version, expected_mcp_spec):
|
|
30
|
+
"""Require a verified source MCP contract before accepting cached registration."""
|
|
31
|
+
if component in MCP_COMPONENTS and expected_mcp_spec is None:
|
|
32
|
+
return False
|
|
33
|
+
return valid_registration(
|
|
34
|
+
plugin,
|
|
35
|
+
component,
|
|
36
|
+
version,
|
|
37
|
+
expected_mcp_spec=expected_mcp_spec,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
30
41
|
def _legacy_state(
|
|
31
42
|
manifest,
|
|
32
43
|
component,
|
|
@@ -35,6 +46,7 @@ def _legacy_state(
|
|
|
35
46
|
admission_error,
|
|
36
47
|
host_error,
|
|
37
48
|
codex_home=None,
|
|
49
|
+
expected_mcp_spec: McpSpec | None = None,
|
|
38
50
|
):
|
|
39
51
|
"""Preserve the coarse state contract while checking installed package integrity."""
|
|
40
52
|
record = records.get(component)
|
|
@@ -46,11 +58,15 @@ def _legacy_state(
|
|
|
46
58
|
if relation != 0:
|
|
47
59
|
return "stale" if relation < 0 else "incompatible"
|
|
48
60
|
plugin = _health_plugin(manifest, component, record, codex_home)
|
|
49
|
-
if plugin is None or not _required_files(
|
|
61
|
+
if plugin is None or not _required_files(
|
|
62
|
+
manifest, component, plugin, codex_home, expected_mcp_spec
|
|
63
|
+
):
|
|
50
64
|
return "stale"
|
|
51
65
|
if not manifest_is_valid(
|
|
52
66
|
plugin, manifest.component(component).plugin, record_version(record)
|
|
53
|
-
) or not
|
|
67
|
+
) or not _selected_registration_valid(
|
|
68
|
+
plugin, component, record_version(record), expected_mcp_spec
|
|
69
|
+
):
|
|
54
70
|
return "incompatible"
|
|
55
71
|
if component == "core" and any(
|
|
56
72
|
os.path.lexists(plugin / path)
|
|
@@ -62,9 +78,21 @@ def _legacy_state(
|
|
|
62
78
|
return "healthy"
|
|
63
79
|
|
|
64
80
|
|
|
65
|
-
def _required_files(
|
|
81
|
+
def _required_files(
|
|
82
|
+
manifest, component, plugin, codex_home=None, expected_mcp_spec=None
|
|
83
|
+
):
|
|
84
|
+
# The selected source contract controls whether doctor expects registration files.
|
|
85
|
+
if component in MCP_COMPONENTS and expected_mcp_spec is None:
|
|
86
|
+
return False
|
|
87
|
+
mcp_paths = (
|
|
88
|
+
tuple(path.as_posix() for path in expected_mcp_spec.surface_paths)
|
|
89
|
+
if component in MCP_COMPONENTS
|
|
90
|
+
else ()
|
|
91
|
+
)
|
|
66
92
|
paths = (
|
|
67
|
-
manifest.component(component).asset.required_paths
|
|
93
|
+
manifest.component(component).asset.required_paths
|
|
94
|
+
+ SURFACE_PATHS[component]
|
|
95
|
+
+ mcp_paths
|
|
68
96
|
)
|
|
69
97
|
return all(
|
|
70
98
|
(plugin / path).is_file() and not (plugin / path).is_symlink() for path in paths
|
{getcodexy-1.13.4 → getcodexy-2.0.0}/src/codexy_runtime_tools/component_hook_registration.py
RENAMED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from .component_core_hooks import (
|
|
4
4
|
COMMAND_HOOKS as _CORE_COMMAND_HOOKS,
|
|
5
|
-
LIFECYCLE_HOOKS as _CORE_LIFECYCLE_HOOKS,
|
|
6
|
-
POST_TOOL_HOOKS as _CORE_POST_TOOL_HOOKS,
|
|
7
5
|
LAUNCHERS as CORE_HOOK_LAUNCHERS,
|
|
8
6
|
)
|
|
9
7
|
|
|
@@ -27,11 +25,6 @@ def _command_hook(
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
|
|
30
|
-
def _lifecycle_hook(launcher_stem: str, event: str) -> dict[str, object]:
|
|
31
|
-
command = f'"${{PLUGIN_ROOT}}/hooks/{launcher_stem}'
|
|
32
|
-
return {"hooks": [_hook(f'{command}.sh" {event}', f'{command}.cmd" {event}', 3)]}
|
|
33
|
-
|
|
34
|
-
|
|
35
28
|
def _bash_hooks(event: str) -> list[dict[str, object]]:
|
|
36
29
|
return [_command_hook("^Bash$", "codexy-destructive-command", event)]
|
|
37
30
|
|
|
@@ -72,29 +65,14 @@ _TITLE_MATCHERS = (
|
|
|
72
65
|
|
|
73
66
|
|
|
74
67
|
def _core_hooks() -> dict[str, object]:
|
|
75
|
-
"""Build
|
|
68
|
+
"""Build the retained core routing and ownership hook event groups."""
|
|
76
69
|
command = lambda event: [
|
|
77
70
|
_command_hook(matcher, stem, event) for matcher, stem in _CORE_COMMAND_HOOKS
|
|
78
71
|
]
|
|
79
|
-
lifecycle = lambda event: [
|
|
80
|
-
_command_hook(matcher, stem, event) for matcher, stem in _CORE_LIFECYCLE_HOOKS
|
|
81
|
-
]
|
|
82
|
-
post_tool = [
|
|
83
|
-
_command_hook(matcher, stem, "PostToolUse", 3)
|
|
84
|
-
for matcher, stem in _CORE_POST_TOOL_HOOKS
|
|
85
|
-
]
|
|
86
72
|
return {
|
|
87
73
|
"hooks": {
|
|
88
74
|
"PermissionRequest": command("PermissionRequest"),
|
|
89
|
-
"PreToolUse": command("PreToolUse")
|
|
90
|
-
"PostToolUse": post_tool,
|
|
91
|
-
"Interrupt": [
|
|
92
|
-
_lifecycle_hook(stem, "Interrupt") for _, stem in _CORE_LIFECYCLE_HOOKS
|
|
93
|
-
],
|
|
94
|
-
"UserPromptSubmit": [
|
|
95
|
-
_lifecycle_hook(stem, "UserPromptSubmit")
|
|
96
|
-
for _, stem in _CORE_LIFECYCLE_HOOKS
|
|
97
|
-
],
|
|
75
|
+
"PreToolUse": command("PreToolUse"),
|
|
98
76
|
}
|
|
99
77
|
}
|
|
100
78
|
|