getcodexy 1.7.0__tar.gz → 1.7.2__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.7.0 → getcodexy-1.7.2}/PKG-INFO +1 -1
- {getcodexy-1.7.0 → getcodexy-1.7.2}/pyproject.toml +1 -1
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/_version_data/uv.lock +1 -1
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component-manifest.json +11 -9
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_cli.py +7 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_health.py +17 -3
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_health_support.py +29 -12
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_inspection.py +5 -23
- getcodexy-1.7.2/src/codexy_runtime_tools/component_inspection_host.py +33 -0
- getcodexy-1.7.2/src/codexy_runtime_tools/component_lifecycle_mcp.py +45 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_recovery.py +9 -4
- getcodexy-1.7.2/src/codexy_runtime_tools/component_mcp_cache.py +84 -0
- getcodexy-1.7.2/src/codexy_runtime_tools/component_mcp_materialization.py +201 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_registration_health.py +11 -26
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_watcher_probe.py +2 -25
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/github_pre_session.py +13 -1
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/installer.py +4 -1
- getcodexy-1.7.2/src/codexy_runtime_tools/mcp_bootstrap.py +90 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/pre_session.py +8 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/uv.lock +1 -1
- {getcodexy-1.7.0 → getcodexy-1.7.2}/README.md +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/build_backend.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/__init__.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/activation_transaction.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/cache.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_capability_observation.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_capability_probe.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_capability_probe_process.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_core_hooks.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_hook_activation.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_hook_activation_expected.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_hook_activation_host.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_hook_activation_validation.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_inspection_types.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_integrity.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_interactive_probe.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_inventory_classification.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_inventory_records.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_admission.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_finish.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_interlock.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_operation.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_preflight.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_support.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_terminal.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_manifest.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_manifest_dependencies.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_manifest_values.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_mcp_probe.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_observed_inventory.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_resolver.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transaction_durability.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transaction_identity.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transaction_receipts.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transaction_snapshot.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transaction_state.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transition_journal.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transition_model.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transition_receipt.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_transition_rejections.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/contract.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/github_checks.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/identity.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/local_marketplace.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/marketplace_identity.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/marketplace_repin.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/monolith_baseline.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/monolith_classifier.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/monolith_migration.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/monolith_migration_host.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/monolith_migration_plan.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/monolith_migration_state.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/package.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/package_archive.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/plugin_resolution.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/release_contract_validation.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/release_provenance.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/release_watcher_contract.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/runtime.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/runtime_configuration.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/source.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/title_policy.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/updater.py +0 -0
- {getcodexy-1.7.0 → getcodexy-1.7.2}/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 = "1.7.
|
|
8
|
+
version = "1.7.2"
|
|
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": "1.7.
|
|
32
|
+
"version": "1.7.2",
|
|
33
33
|
"dependencies": [],
|
|
34
34
|
"asset": {
|
|
35
35
|
"pluginId": "codexy@codexy",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
".codex-plugin/plugin.json",
|
|
39
39
|
"assets/codexy-icon.png",
|
|
40
40
|
".mcp.json",
|
|
41
|
+
"mcp/codexy_mcp_bootstrap.py",
|
|
41
42
|
"mcp/codexy-mcp-watcher.sh",
|
|
42
43
|
"mcp/codexy-mcp-watcher.cmd",
|
|
43
44
|
"skills/dreaming/scripts/resumable-context-capsule.sh",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
{
|
|
50
51
|
"id": "github",
|
|
51
52
|
"plugin": "codexy-github",
|
|
52
|
-
"version": "1.7.
|
|
53
|
+
"version": "1.7.2",
|
|
53
54
|
"dependencies": [
|
|
54
55
|
"core"
|
|
55
56
|
],
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
{
|
|
66
67
|
"id": "devtools",
|
|
67
68
|
"plugin": "codexy-devtools",
|
|
68
|
-
"version": "1.7.
|
|
69
|
+
"version": "1.7.2",
|
|
69
70
|
"dependencies": [
|
|
70
71
|
"core"
|
|
71
72
|
],
|
|
@@ -74,7 +75,8 @@
|
|
|
74
75
|
"packageRoot": "plugins/codexy-devtools",
|
|
75
76
|
"requiredPaths": [
|
|
76
77
|
".codex-plugin/plugin.json",
|
|
77
|
-
".mcp.json"
|
|
78
|
+
".mcp.json",
|
|
79
|
+
"mcp/codexy_mcp_bootstrap.py"
|
|
78
80
|
]
|
|
79
81
|
}
|
|
80
82
|
}
|
|
@@ -82,27 +84,27 @@
|
|
|
82
84
|
"compatibleCombinations": [
|
|
83
85
|
{
|
|
84
86
|
"components": [],
|
|
85
|
-
"version": "1.7.
|
|
87
|
+
"version": "1.7.2"
|
|
86
88
|
},
|
|
87
89
|
{
|
|
88
90
|
"components": [
|
|
89
91
|
"core"
|
|
90
92
|
],
|
|
91
|
-
"version": "1.7.
|
|
93
|
+
"version": "1.7.2"
|
|
92
94
|
},
|
|
93
95
|
{
|
|
94
96
|
"components": [
|
|
95
97
|
"core",
|
|
96
98
|
"github"
|
|
97
99
|
],
|
|
98
|
-
"version": "1.7.
|
|
100
|
+
"version": "1.7.2"
|
|
99
101
|
},
|
|
100
102
|
{
|
|
101
103
|
"components": [
|
|
102
104
|
"core",
|
|
103
105
|
"devtools"
|
|
104
106
|
],
|
|
105
|
-
"version": "1.7.
|
|
107
|
+
"version": "1.7.2"
|
|
106
108
|
},
|
|
107
109
|
{
|
|
108
110
|
"components": [
|
|
@@ -110,7 +112,7 @@
|
|
|
110
112
|
"github",
|
|
111
113
|
"devtools"
|
|
112
114
|
],
|
|
113
|
-
"version": "1.7.
|
|
115
|
+
"version": "1.7.2"
|
|
114
116
|
}
|
|
115
117
|
]
|
|
116
118
|
}
|
|
@@ -138,6 +138,13 @@ def _human(command: str, receipt: dict[str, object]) -> str:
|
|
|
138
138
|
)
|
|
139
139
|
or "none",
|
|
140
140
|
)
|
|
141
|
+
if command in {"install", "update", "remove", "bootstrap"} and receipt.get(
|
|
142
|
+
"outcome"
|
|
143
|
+
) in {"completed", "pending-action"}:
|
|
144
|
+
return (
|
|
145
|
+
f"getcodexy {command}: {receipt['outcome']}; "
|
|
146
|
+
"start a fresh Codex task to refresh installed plugins and MCP tools"
|
|
147
|
+
)
|
|
141
148
|
if command == "doctor":
|
|
142
149
|
health = receipt.get("component_health", [])
|
|
143
150
|
summary = (
|
|
@@ -10,11 +10,12 @@ from .component_capability_probe import (
|
|
|
10
10
|
)
|
|
11
11
|
from .component_capability_observation import component_observations
|
|
12
12
|
from .component_hook_activation import ACTIVATION_STATES
|
|
13
|
+
from .component_mcp_materialization import MCP_COMPONENTS, valid_component_mcp_cache
|
|
13
14
|
from .component_health_support import (
|
|
14
15
|
_authority_valid,
|
|
16
|
+
_health_plugin,
|
|
15
17
|
_legacy_state,
|
|
16
18
|
_observed,
|
|
17
|
-
_plugin_root,
|
|
18
19
|
manifest_is_valid,
|
|
19
20
|
record_version,
|
|
20
21
|
version_relation,
|
|
@@ -31,6 +32,7 @@ def health(
|
|
|
31
32
|
admission_error: str | None,
|
|
32
33
|
host_error: bool,
|
|
33
34
|
activation: dict[str, str] | None = None,
|
|
35
|
+
codex_home=None,
|
|
34
36
|
) -> list[dict[str, object]]:
|
|
35
37
|
expected = set(recorded or ()) | set(actual)
|
|
36
38
|
return [
|
|
@@ -42,6 +44,7 @@ def health(
|
|
|
42
44
|
admission_error,
|
|
43
45
|
host_error,
|
|
44
46
|
activation,
|
|
47
|
+
codex_home,
|
|
45
48
|
)
|
|
46
49
|
for component in manifest.component_ids
|
|
47
50
|
if component in expected
|
|
@@ -56,10 +59,11 @@ def _component_health(
|
|
|
56
59
|
admission_error,
|
|
57
60
|
host_error,
|
|
58
61
|
activation,
|
|
62
|
+
codex_home,
|
|
59
63
|
):
|
|
60
64
|
record = records.get(component)
|
|
61
65
|
installed = component in actual
|
|
62
|
-
plugin =
|
|
66
|
+
plugin = _health_plugin(manifest, component, record, codex_home)
|
|
63
67
|
configured = bool(
|
|
64
68
|
installed
|
|
65
69
|
and plugin
|
|
@@ -67,11 +71,21 @@ def _component_health(
|
|
|
67
71
|
plugin, manifest.component(component).plugin, record_version(record)
|
|
68
72
|
)
|
|
69
73
|
and valid_registration(plugin, component)
|
|
74
|
+
and (
|
|
75
|
+
component not in MCP_COMPONENTS
|
|
76
|
+
or valid_component_mcp_cache(codex_home, component, manifest.version)
|
|
77
|
+
)
|
|
70
78
|
)
|
|
71
79
|
result = dict(
|
|
72
80
|
component=component,
|
|
73
81
|
state=_legacy_state(
|
|
74
|
-
manifest,
|
|
82
|
+
manifest,
|
|
83
|
+
component,
|
|
84
|
+
actual,
|
|
85
|
+
records,
|
|
86
|
+
admission_error,
|
|
87
|
+
host_error,
|
|
88
|
+
codex_home,
|
|
75
89
|
),
|
|
76
90
|
installed=installed,
|
|
77
91
|
configured=configured,
|
|
@@ -4,6 +4,11 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from .component_mcp_materialization import (
|
|
8
|
+
MCP_COMPONENTS,
|
|
9
|
+
component_cache_plugin,
|
|
10
|
+
mcp_spec,
|
|
11
|
+
)
|
|
7
12
|
from .component_manifest import ComponentManifest
|
|
8
13
|
from .component_registration_health import valid_registration
|
|
9
14
|
from .component_resolver import ComponentResolutionError, compare_versions
|
|
@@ -14,17 +19,23 @@ SURFACE_PATHS = {
|
|
|
14
19
|
"agents/catalog.toml",
|
|
15
20
|
"hooks/hooks.json",
|
|
16
21
|
"skills/wiki/SKILL.md",
|
|
17
|
-
".
|
|
18
|
-
"mcp/codexy-mcp-watcher.sh",
|
|
19
|
-
"mcp/codexy-mcp-watcher.cmd",
|
|
22
|
+
*(path.as_posix() for path in mcp_spec("core").surface_paths),
|
|
20
23
|
),
|
|
21
24
|
"github": ("agents/catalog.toml", "hooks/hooks.json"),
|
|
22
|
-
"devtools": ("
|
|
25
|
+
"devtools": tuple(path.as_posix() for path in mcp_spec("devtools").surface_paths),
|
|
23
26
|
}
|
|
24
27
|
AUTHORITY_KEYS = ("authority", "artifact_authority", "artifactAuthority")
|
|
25
28
|
|
|
26
29
|
|
|
27
|
-
def _legacy_state(
|
|
30
|
+
def _legacy_state(
|
|
31
|
+
manifest,
|
|
32
|
+
component,
|
|
33
|
+
actual,
|
|
34
|
+
records,
|
|
35
|
+
admission_error,
|
|
36
|
+
host_error,
|
|
37
|
+
codex_home=None,
|
|
38
|
+
):
|
|
28
39
|
record = records.get(component)
|
|
29
40
|
if admission_error or host_error:
|
|
30
41
|
return "incompatible"
|
|
@@ -33,17 +44,13 @@ def _legacy_state(manifest, component, actual, records, admission_error, host_er
|
|
|
33
44
|
relation = version_relation(manifest, record)
|
|
34
45
|
if relation != 0:
|
|
35
46
|
return "stale" if relation < 0 else "incompatible"
|
|
36
|
-
plugin =
|
|
37
|
-
if plugin is None or not _required_files(manifest, component, plugin):
|
|
47
|
+
plugin = _health_plugin(manifest, component, record, codex_home)
|
|
48
|
+
if plugin is None or not _required_files(manifest, component, plugin, codex_home):
|
|
38
49
|
return "stale"
|
|
39
50
|
if not manifest_is_valid(
|
|
40
51
|
plugin, manifest.component(component).plugin, record_version(record)
|
|
41
52
|
) or not valid_registration(plugin, component):
|
|
42
53
|
return "incompatible"
|
|
43
|
-
if component == "devtools" and not os.access(
|
|
44
|
-
plugin / "mcp/codexy-mcp-devtools", os.X_OK
|
|
45
|
-
):
|
|
46
|
-
return "stale"
|
|
47
54
|
if component == "core" and any(
|
|
48
55
|
os.path.lexists(plugin / path)
|
|
49
56
|
for path in (".codex/lsp-client.json", "lsp", "runtime-release.json")
|
|
@@ -54,7 +61,7 @@ def _legacy_state(manifest, component, actual, records, admission_error, host_er
|
|
|
54
61
|
return "healthy"
|
|
55
62
|
|
|
56
63
|
|
|
57
|
-
def _required_files(manifest, component, plugin):
|
|
64
|
+
def _required_files(manifest, component, plugin, codex_home=None):
|
|
58
65
|
paths = (
|
|
59
66
|
manifest.component(component).asset.required_paths + SURFACE_PATHS[component]
|
|
60
67
|
)
|
|
@@ -63,6 +70,16 @@ def _required_files(manifest, component, plugin):
|
|
|
63
70
|
)
|
|
64
71
|
|
|
65
72
|
|
|
73
|
+
def _health_plugin(manifest, component, record, codex_home=None):
|
|
74
|
+
plugin = _plugin_root(record)
|
|
75
|
+
if component not in MCP_COMPONENTS or codex_home is None:
|
|
76
|
+
return plugin
|
|
77
|
+
cache_root = codex_home / "plugins" / "cache"
|
|
78
|
+
if cache_root.is_symlink() or not cache_root.is_dir():
|
|
79
|
+
return plugin
|
|
80
|
+
return component_cache_plugin(codex_home, component, manifest.version)
|
|
81
|
+
|
|
82
|
+
|
|
66
83
|
def _plugin_root(record):
|
|
67
84
|
source = (record or {}).get("source")
|
|
68
85
|
value = source.get("path") if isinstance(source, dict) else None
|
|
@@ -6,12 +6,12 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
import subprocess
|
|
9
|
-
from enum import Enum
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import Callable
|
|
12
11
|
|
|
13
12
|
from .component_hook_activation import HookLister, activation_for_inventory
|
|
14
13
|
from .component_health import health as _health
|
|
14
|
+
from .component_inspection_host import ProbeStage, host as _host
|
|
15
15
|
from .component_inspection_types import InspectionReport
|
|
16
16
|
from .component_inventory_classification import ComponentResolutionError
|
|
17
17
|
from .component_inventory_classification import classify_installed_inventory
|
|
@@ -19,14 +19,13 @@ from .component_manifest import ComponentManifest, load_component_manifest
|
|
|
19
19
|
from .component_observed_inventory import observe_installed_inventory
|
|
20
20
|
from .component_resolver import admit_installed_inventory, canonical_components
|
|
21
21
|
from .component_transaction_state import read_inventory
|
|
22
|
-
from .github_pre_session import trusted_codex
|
|
23
22
|
from .plugin_resolution import (
|
|
24
23
|
MarketplaceBinding,
|
|
25
24
|
marketplace_identity,
|
|
26
25
|
named_marketplace,
|
|
27
26
|
validate_local_marketplace,
|
|
28
27
|
)
|
|
29
|
-
from .pre_session import
|
|
28
|
+
from .pre_session import _json
|
|
30
29
|
from .updater import _absolute, _validate_real_path
|
|
31
30
|
|
|
32
31
|
|
|
@@ -35,12 +34,6 @@ STATUS_SCHEMA = "getcodexy.status.v1"
|
|
|
35
34
|
DOCTOR_SCHEMA = "getcodexy.doctor.v1"
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
class ProbeStage(str, Enum):
|
|
39
|
-
EXECUTABLE = "codex-executable"
|
|
40
|
-
PLUGIN_LIST = "codex-plugin-list"
|
|
41
|
-
MARKETPLACE_LIST = "codex-marketplace-list"
|
|
42
|
-
|
|
43
|
-
|
|
44
37
|
def status(
|
|
45
38
|
codex_home: str | os.PathLike[str],
|
|
46
39
|
*,
|
|
@@ -71,7 +64,8 @@ def doctor(
|
|
|
71
64
|
hook_lister: HookLister | None = None,
|
|
72
65
|
) -> dict[str, object]:
|
|
73
66
|
"""Inspect canonical managed files and return actionable repairs."""
|
|
74
|
-
|
|
67
|
+
home = _absolute(codex_home)
|
|
68
|
+
report = _inspect(home, codex, runner, hook_lister)
|
|
75
69
|
missing_requirements: list[str] = (
|
|
76
70
|
[report["host_error"]] if report["host_error"] else []
|
|
77
71
|
)
|
|
@@ -97,6 +91,7 @@ def doctor(
|
|
|
97
91
|
report["admission_error"],
|
|
98
92
|
bool(report["host_error"]),
|
|
99
93
|
report["activation"],
|
|
94
|
+
codex_home=home,
|
|
100
95
|
),
|
|
101
96
|
"source_of_truth": "installed-component-inventory",
|
|
102
97
|
"errors": report["errors"],
|
|
@@ -177,19 +172,6 @@ def _inspect(
|
|
|
177
172
|
}
|
|
178
173
|
|
|
179
174
|
|
|
180
|
-
def _host(
|
|
181
|
-
home: Path, codex: Path | None, runner: Runner | None
|
|
182
|
-
) -> tuple[Path, Runner, None] | tuple[None, None, ProbeStage]:
|
|
183
|
-
try:
|
|
184
|
-
return (
|
|
185
|
-
trusted_codex(codex or _find_codex()),
|
|
186
|
-
runner or (lambda command: _run(command, home)),
|
|
187
|
-
None,
|
|
188
|
-
)
|
|
189
|
-
except (OSError, RuntimeError, ValueError):
|
|
190
|
-
return None, None, ProbeStage.EXECUTABLE
|
|
191
|
-
|
|
192
|
-
|
|
193
175
|
def _recorded(
|
|
194
176
|
home: Path,
|
|
195
177
|
) -> tuple[tuple[str, ...] | None, dict[str, object], str | None]:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Trusted host setup used by component inspection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
from .github_pre_session import trusted_codex
|
|
11
|
+
from .pre_session import _find_codex, _run
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Runner = Callable[[list[str]], subprocess.CompletedProcess[str]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProbeStage(str, Enum):
|
|
18
|
+
EXECUTABLE = "codex-executable"
|
|
19
|
+
PLUGIN_LIST = "codex-plugin-list"
|
|
20
|
+
MARKETPLACE_LIST = "codex-marketplace-list"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def host(
|
|
24
|
+
home: Path, codex: Path | None, runner: Runner | None
|
|
25
|
+
) -> tuple[Path, Runner, None] | tuple[None, None, ProbeStage]:
|
|
26
|
+
try:
|
|
27
|
+
return (
|
|
28
|
+
trusted_codex(codex or _find_codex()),
|
|
29
|
+
runner or (lambda command: _run(command, home)),
|
|
30
|
+
None,
|
|
31
|
+
)
|
|
32
|
+
except (OSError, RuntimeError, ValueError):
|
|
33
|
+
return None, None, ProbeStage.EXECUTABLE
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""MCP source and cache materialization for component lifecycle operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .component_manifest import ComponentManifest
|
|
8
|
+
from .component_mcp_materialization import (
|
|
9
|
+
MCP_COMPONENTS,
|
|
10
|
+
materialize_component_mcp,
|
|
11
|
+
materialize_component_mcp_cache,
|
|
12
|
+
)
|
|
13
|
+
from .plugin_resolution import MarketplaceBinding, marketplace_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def materialize_mcp_sources(
|
|
17
|
+
root: MarketplaceBinding,
|
|
18
|
+
manifest: ComponentManifest,
|
|
19
|
+
components: tuple[str, ...],
|
|
20
|
+
) -> None:
|
|
21
|
+
marketplace = marketplace_path(root)
|
|
22
|
+
for component in manifest.component_ids:
|
|
23
|
+
if component not in components or component not in MCP_COMPONENTS:
|
|
24
|
+
continue
|
|
25
|
+
plugin = marketplace / "plugins" / manifest.component(component).plugin
|
|
26
|
+
materialize_component_mcp(plugin, component, manifest.version)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def materialize_mcp_caches(
|
|
30
|
+
home: Path,
|
|
31
|
+
root: MarketplaceBinding,
|
|
32
|
+
manifest: ComponentManifest,
|
|
33
|
+
components: tuple[str, ...],
|
|
34
|
+
) -> None:
|
|
35
|
+
marketplace = marketplace_path(root)
|
|
36
|
+
for component in manifest.component_ids:
|
|
37
|
+
if component not in components or component not in MCP_COMPONENTS:
|
|
38
|
+
continue
|
|
39
|
+
source = marketplace / "plugins" / manifest.component(component).plugin
|
|
40
|
+
materialize_component_mcp_cache(
|
|
41
|
+
home,
|
|
42
|
+
component,
|
|
43
|
+
manifest.version,
|
|
44
|
+
source_plugin=source,
|
|
45
|
+
)
|
{getcodexy-1.7.0 → getcodexy-1.7.2}/src/codexy_runtime_tools/component_lifecycle_recovery.py
RENAMED
|
@@ -8,6 +8,7 @@ from typing import Callable
|
|
|
8
8
|
|
|
9
9
|
from .component_hook_activation import HookLister
|
|
10
10
|
from .component_lifecycle_finish import finish_committed
|
|
11
|
+
from .component_lifecycle_mcp import materialize_mcp_caches, materialize_mcp_sources
|
|
11
12
|
from .component_lifecycle_preflight import existing_marketplace
|
|
12
13
|
from .component_manifest import ComponentManifest
|
|
13
14
|
from .component_resolver import (
|
|
@@ -115,7 +116,7 @@ def rollback_or_raise(
|
|
|
115
116
|
) -> None:
|
|
116
117
|
try:
|
|
117
118
|
write_journal(home, journal.with_phase("rolling-back"))
|
|
118
|
-
restore_selection(executable, invoke, manifest, root, journal.before)
|
|
119
|
+
restore_selection(home, executable, invoke, manifest, root, journal.before)
|
|
119
120
|
if (
|
|
120
121
|
selection(manifest, list_installed(executable, invoke), root)
|
|
121
122
|
!= journal.before
|
|
@@ -160,6 +161,7 @@ def write_completed(
|
|
|
160
161
|
|
|
161
162
|
|
|
162
163
|
def restore_selection(
|
|
164
|
+
home: Path,
|
|
163
165
|
executable: Path,
|
|
164
166
|
invoke: Runner,
|
|
165
167
|
manifest: ComponentManifest,
|
|
@@ -167,12 +169,14 @@ def restore_selection(
|
|
|
167
169
|
before: tuple[str, ...],
|
|
168
170
|
) -> None:
|
|
169
171
|
current = selection(manifest, list_installed(executable, invoke), root)
|
|
172
|
+
materialize_mcp_sources(root, manifest, before)
|
|
170
173
|
for component in before:
|
|
171
174
|
if component not in current:
|
|
172
175
|
mutate(executable, invoke, "add", manifest, component)
|
|
173
176
|
for component in reversed(manifest.component_ids):
|
|
174
177
|
if component in current and component not in before:
|
|
175
178
|
mutate(executable, invoke, "remove", manifest, component)
|
|
179
|
+
materialize_mcp_caches(home, root, manifest, before)
|
|
176
180
|
|
|
177
181
|
|
|
178
182
|
def apply_forward(
|
|
@@ -196,13 +200,14 @@ def apply_forward(
|
|
|
196
200
|
manifest.version,
|
|
197
201
|
home,
|
|
198
202
|
)
|
|
203
|
+
materialize_mcp_sources(root, manifest, journal.target)
|
|
199
204
|
for component in adds:
|
|
200
205
|
mutate(executable, invoke, "add", manifest, component)
|
|
201
206
|
for component in removes:
|
|
202
207
|
mutate(executable, invoke, "remove", manifest, component)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
208
|
+
installed = list_installed(executable, invoke)
|
|
209
|
+
materialize_mcp_caches(home, root, manifest, journal.target)
|
|
210
|
+
return verify_post_operation_inventory(manifest, installed, journal.target, root)
|
|
206
211
|
|
|
207
212
|
|
|
208
213
|
def mutate(
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Safe synchronization helpers for component MCP cache surfaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import stat
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Iterable
|
|
10
|
+
|
|
11
|
+
from .updater import _absolute, _validate_real_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _needs_executable(path: Path) -> bool:
|
|
15
|
+
return path.name != "runtime-platform.sh" and path.suffix in {"", ".sh"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _valid_surface_file(path: Path, *, executable: bool) -> bool:
|
|
19
|
+
try:
|
|
20
|
+
_validate_real_path(_absolute(path), require_exists=True)
|
|
21
|
+
metadata = path.lstat()
|
|
22
|
+
except (OSError, ValueError):
|
|
23
|
+
return False
|
|
24
|
+
if not stat.S_ISREG(metadata.st_mode) or stat.S_ISLNK(metadata.st_mode):
|
|
25
|
+
return False
|
|
26
|
+
return not executable or os.name == "nt" or os.access(path, os.X_OK)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _require_directory(path: Path, label: str) -> None:
|
|
30
|
+
_validate_real_path(_absolute(path), require_exists=True)
|
|
31
|
+
metadata = path.lstat()
|
|
32
|
+
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
|
|
33
|
+
raise RuntimeError(f"{label} is not a regular directory: {path}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _sync_surface(source: Path, target: Path, paths: Iterable[Path]) -> None:
|
|
37
|
+
_require_directory(source, "MCP source plugin")
|
|
38
|
+
_require_directory(target, "MCP cache plugin")
|
|
39
|
+
for relative in paths:
|
|
40
|
+
source_path, target_path = source / relative, target / relative
|
|
41
|
+
if not _valid_surface_file(source_path, executable=_needs_executable(relative)):
|
|
42
|
+
raise RuntimeError(f"MCP source file is missing or invalid: {source_path}")
|
|
43
|
+
_validate_real_path(_absolute(target_path.parent), require_exists=False)
|
|
44
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
_validate_real_path(_absolute(target_path.parent), require_exists=True)
|
|
46
|
+
if _same_file(source_path, target_path):
|
|
47
|
+
continue
|
|
48
|
+
if os.path.lexists(target_path):
|
|
49
|
+
metadata = target_path.lstat()
|
|
50
|
+
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
|
|
51
|
+
raise RuntimeError(
|
|
52
|
+
f"MCP cache file is not a regular file: {target_path}"
|
|
53
|
+
)
|
|
54
|
+
_atomic_copy(source_path, target_path)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _same_file(source: Path, target: Path) -> bool:
|
|
58
|
+
try:
|
|
59
|
+
target_metadata = target.lstat()
|
|
60
|
+
return (
|
|
61
|
+
stat.S_ISREG(target_metadata.st_mode)
|
|
62
|
+
and not stat.S_ISLNK(target_metadata.st_mode)
|
|
63
|
+
and source.read_bytes() == target.read_bytes()
|
|
64
|
+
and stat.S_IMODE(source.stat().st_mode)
|
|
65
|
+
== stat.S_IMODE(target_metadata.st_mode)
|
|
66
|
+
)
|
|
67
|
+
except OSError:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _atomic_copy(source: Path, target: Path) -> None:
|
|
72
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
73
|
+
prefix=f".{target.name}.", suffix=".tmp", dir=target.parent
|
|
74
|
+
)
|
|
75
|
+
temporary = Path(temporary_name)
|
|
76
|
+
try:
|
|
77
|
+
with os.fdopen(descriptor, "wb") as output:
|
|
78
|
+
output.write(source.read_bytes())
|
|
79
|
+
output.flush()
|
|
80
|
+
os.fsync(output.fileno())
|
|
81
|
+
temporary.chmod(stat.S_IMODE(source.stat().st_mode))
|
|
82
|
+
os.replace(temporary, target)
|
|
83
|
+
finally:
|
|
84
|
+
temporary.unlink(missing_ok=True)
|