sequential-hooks 0.1.0__py3-none-any.whl

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.
Files changed (43) hide show
  1. plugins/agy/_sequential_hooks/__init__.py +1 -0
  2. plugins/agy/_sequential_hooks/_adapter.py +566 -0
  3. plugins/agy/_sequential_hooks/_doctor.py +413 -0
  4. plugins/claude/_sequential_hooks/__init__.py +1 -0
  5. plugins/claude/_sequential_hooks/_adapter.py +560 -0
  6. plugins/claude/_sequential_hooks/_doctor.py +455 -0
  7. plugins/codex/_sequential_hooks/__init__.py +1 -0
  8. plugins/codex/_sequential_hooks/_adapter.py +466 -0
  9. plugins/codex/_sequential_hooks/_doctor.py +563 -0
  10. sequential_hooks/__init__.py +3 -0
  11. sequential_hooks/__main__.py +5 -0
  12. sequential_hooks/_arguments.py +222 -0
  13. sequential_hooks/_cleanup.py +88 -0
  14. sequential_hooks/_cli.py +440 -0
  15. sequential_hooks/_containment/__init__.py +410 -0
  16. sequential_hooks/_containment/_posix.py +236 -0
  17. sequential_hooks/_containment/_uncontained.py +239 -0
  18. sequential_hooks/_containment/_windows.py +853 -0
  19. sequential_hooks/_containment/_windows_api.py +570 -0
  20. sequential_hooks/_containment/_windows_launcher.py +189 -0
  21. sequential_hooks/_diagnostics.py +265 -0
  22. sequential_hooks/_doctor/__init__.py +342 -0
  23. sequential_hooks/_doctor/_command.py +469 -0
  24. sequential_hooks/_doctor/_common.py +448 -0
  25. sequential_hooks/_doctor/_types.py +94 -0
  26. sequential_hooks/_downstream.py +181 -0
  27. sequential_hooks/_executable.py +187 -0
  28. sequential_hooks/_executor.py +825 -0
  29. sequential_hooks/_registry.py +103 -0
  30. sequential_hooks/_runner.py +48 -0
  31. sequential_hooks/_types.py +125 -0
  32. sequential_hooks/hosts/__init__.py +130 -0
  33. sequential_hooks/hosts/_contract.py +434 -0
  34. sequential_hooks/hosts/_inspection.py +78 -0
  35. sequential_hooks/hosts/_json.py +150 -0
  36. sequential_hooks/hosts/_records.py +273 -0
  37. sequential_hooks/hosts/_skeleton.py +1326 -0
  38. sequential_hooks/py.typed +0 -0
  39. sequential_hooks-0.1.0.dist-info/METADATA +90 -0
  40. sequential_hooks-0.1.0.dist-info/RECORD +43 -0
  41. sequential_hooks-0.1.0.dist-info/WHEEL +4 -0
  42. sequential_hooks-0.1.0.dist-info/entry_points.txt +7 -0
  43. sequential_hooks-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,103 @@
1
+ """Discover and load host adapters through entry points."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import import_module
6
+ from typing import TYPE_CHECKING, cast
7
+
8
+ from sequential_hooks.hosts import API_VERSION, HostSpec
9
+
10
+ if TYPE_CHECKING:
11
+ from importlib.metadata import EntryPoints
12
+
13
+ _ENTRY_POINT_GROUP = 'sequential_hooks.hosts'
14
+ _REQUIRED_ATTRIBUTES = (
15
+ 'adapter',
16
+ 'configuration_response',
17
+ 'doctor_loader',
18
+ 'events',
19
+ 'name',
20
+ 'payload_event',
21
+ 'requires_event',
22
+ )
23
+
24
+
25
+ class HostLoadError(Exception):
26
+ """Report that no functioning host adapter exists for the selected name."""
27
+
28
+
29
+ def _entry_points() -> EntryPoints:
30
+ """Read host entry points without loading metadata during CLI import."""
31
+ # Delay importing metadata until the caller selects or inventories hosts.
32
+ module = import_module('importlib.metadata')
33
+ return cast('EntryPoints', module.entry_points(group=_ENTRY_POINT_GROUP))
34
+
35
+
36
+ def _registered_hosts() -> list[str]:
37
+ """Return sorted names in the host entry-point group."""
38
+ return sorted({entry.name for entry in _entry_points()})
39
+
40
+
41
+ def _validate_host(name: str, spec: object) -> HostSpec:
42
+ """Validate and return one constructed host specification."""
43
+ for attribute in _REQUIRED_ATTRIBUTES:
44
+ if not hasattr(spec, attribute):
45
+ raise HostLoadError(f'host {name!r} does not provide {attribute}')
46
+ version = getattr(spec, 'api_version', None)
47
+ if version != API_VERSION:
48
+ raise HostLoadError(
49
+ f'host {name!r} implements host API {version!r}; '
50
+ f'sequential-hooks requires {API_VERSION}'
51
+ )
52
+ host_spec = cast('HostSpec', spec)
53
+ reported_name = host_spec.name
54
+ if reported_name != name:
55
+ raise HostLoadError(f'host {name!r} reports the name {reported_name!r}')
56
+ return host_spec
57
+
58
+
59
+ def load_host(name: str) -> HostSpec:
60
+ """Load the host registered under one entry-point name.
61
+
62
+ Args:
63
+ name: Host name given to `--agent`.
64
+
65
+ Returns:
66
+ The registered host specification.
67
+
68
+ Raises:
69
+ HostLoadError: If the name is unregistered or registered more than
70
+ once, the entry point cannot be loaded, the object lacks a contract
71
+ attribute, its `api_version` differs from `API_VERSION`, or its
72
+ reported `name` differs from the entry-point name.
73
+ """
74
+ matches = [entry for entry in _entry_points() if entry.name == name]
75
+ if not matches:
76
+ hosts = ', '.join(_registered_hosts()) or 'none'
77
+ raise HostLoadError(f'unknown host {name!r}; registered hosts: {hosts}')
78
+ if len(matches) > 1:
79
+ raise HostLoadError(f'host {name!r} is registered {len(matches)} times')
80
+ try:
81
+ factory = matches[0].load()
82
+ spec = _validate_host(name, factory())
83
+ except HostLoadError:
84
+ raise
85
+ except Exception as error:
86
+ raise HostLoadError(f'host {name!r} failed to load: {error}') from error
87
+ return spec
88
+
89
+
90
+ def host_requirements() -> dict[str, HostSpec]:
91
+ """Return each loadable host specification keyed by its registered name.
92
+
93
+ Returns:
94
+ Names in sorted order; hosts that fail to load are omitted.
95
+ """
96
+ requirements: dict[str, HostSpec] = {}
97
+ for name in _registered_hosts():
98
+ try:
99
+ spec = load_host(name)
100
+ except HostLoadError:
101
+ continue
102
+ requirements[name] = spec
103
+ return requirements
@@ -0,0 +1,48 @@
1
+ """Run ordered child steps through one host adapter."""
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from sequential_hooks import _executor
6
+ from sequential_hooks._types import Step
7
+ from sequential_hooks.hosts import FinalResponse, HostAdapter
8
+
9
+
10
+ def run_pipeline(
11
+ adapter: HostAdapter,
12
+ steps: Iterable[Step],
13
+ executor: _executor.ProcessExecutor,
14
+ step_timeout: float | None,
15
+ *,
16
+ allow_on_failure: bool,
17
+ ) -> FinalResponse:
18
+ """Run ordered steps until the adapter stops or finalizes.
19
+
20
+ Args:
21
+ adapter: Host-neutral interface to native protocol state.
22
+ steps: Child commands in execution order.
23
+ executor: Bounded process executor for each child.
24
+ step_timeout: Independent timeout in seconds for each child, or `None`
25
+ for no step execution deadline.
26
+ allow_on_failure: Whether explicit uncontained fallback is allowed.
27
+
28
+ Returns:
29
+ Terminal or finalized native host response.
30
+
31
+ Raises:
32
+ _executor.ForwardedSignal: If the executor forwards a handled wrapper
33
+ signal, propagated unchanged.
34
+ Exception: Any executor or adapter invariant exception, propagated
35
+ unchanged.
36
+ """
37
+ if not adapter.should_run_steps:
38
+ return adapter.finalize()
39
+ for step in steps:
40
+ result = executor.run(
41
+ step,
42
+ adapter.step_input(),
43
+ step_timeout,
44
+ allow_uncontained=allow_on_failure,
45
+ )
46
+ if adapter.consume(step, result) == 'stop':
47
+ break
48
+ return adapter.finalize()
@@ -0,0 +1,125 @@
1
+ """Define the runner's concrete step, result, and failure records."""
2
+
3
+ from dataclasses import dataclass
4
+ from enum import StrEnum
5
+ from pathlib import Path
6
+
7
+
8
+ class ContainmentState(StrEnum):
9
+ """Describe whether a child owns native process-tree containment.
10
+
11
+ The values are the documented `CONTAINMENT_STATES` strings that adapters
12
+ compare.
13
+
14
+ Attributes:
15
+ CONTAINED: Native process-tree containment is active.
16
+ UNCONTAINED: The child runs through an explicitly allowed fallback
17
+ without process-tree containment.
18
+ """
19
+
20
+ CONTAINED = 'contained'
21
+ UNCONTAINED = 'uncontained'
22
+
23
+
24
+ class FailureKind(StrEnum):
25
+ """Classify an infrastructure failure before adapter interpretation.
26
+
27
+ The values are the documented `FAILURE_KINDS` strings that adapters
28
+ compare.
29
+
30
+ Attributes:
31
+ NOT_FOUND: The executable could not be resolved or started.
32
+ SPAWN: The operating system refused process creation.
33
+ CRASH: The child terminated with a native crash status.
34
+ TIMEOUT: The child exceeded its execution deadline.
35
+ OUTPUT_LIMIT: Standard output exceeded its byte budget.
36
+ OUTPUT_WORKER_TIMEOUT: A pipe worker did not stop within cleanup
37
+ bounds.
38
+ CONTAINMENT: Required process-tree containment could not be
39
+ established.
40
+ UNSUPPORTED_EXECUTABLE: Resolution found an unsupported executable
41
+ shim.
42
+ """
43
+
44
+ NOT_FOUND = 'not_found'
45
+ SPAWN = 'spawn'
46
+ CRASH = 'crash'
47
+ TIMEOUT = 'timeout'
48
+ OUTPUT_LIMIT = 'output_limit'
49
+ OUTPUT_WORKER_TIMEOUT = 'output_worker_timeout'
50
+ CONTAINMENT = 'containment'
51
+ UNSUPPORTED_EXECUTABLE = 'unsupported_executable'
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class Step:
56
+ """Describe one child command in its configured chain position.
57
+
58
+ Args:
59
+ index: One-based position used in diagnostics.
60
+ argv: Literal executable token and arguments.
61
+
62
+ Attributes:
63
+ index: One-based position used in diagnostics.
64
+ argv: Literal executable token and arguments.
65
+ name: Executable basename used in diagnostics.
66
+ """
67
+
68
+ index: int
69
+ argv: tuple[str, ...]
70
+
71
+ @property
72
+ def name(self) -> str:
73
+ """Return the executable basename used in diagnostics.
74
+
75
+ Returns:
76
+ Basename, or the original executable token when it has no basename.
77
+ """
78
+ basename = Path(self.argv[0]).name
79
+ return basename or self.argv[0]
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class StepFailure:
84
+ """Describe one bounded infrastructure failure.
85
+
86
+ Args:
87
+ kind: Stable infrastructure failure category.
88
+ message: Safe user-facing failure detail.
89
+ returncode: Child status when one was observed.
90
+
91
+ Attributes:
92
+ kind: Stable infrastructure failure category.
93
+ message: Safe user-facing failure detail.
94
+ returncode: Child status when one was observed.
95
+ """
96
+
97
+ kind: FailureKind
98
+ message: str
99
+ returncode: int | None = None
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class StepResult:
104
+ """Carry one child's bounded process outcome to an adapter.
105
+
106
+ Args:
107
+ returncode: Child status when one was observed.
108
+ stdout: Bounded child standard-output bytes.
109
+ blocking_stderr: Bounded decoded stderr retained for native responses.
110
+ failure: Infrastructure failure, if one occurred.
111
+ containment: Effective process-tree containment state.
112
+
113
+ Attributes:
114
+ returncode: Child status when one was observed.
115
+ stdout: Bounded child standard-output bytes.
116
+ blocking_stderr: Bounded decoded stderr retained for native responses.
117
+ failure: Infrastructure failure, if one occurred.
118
+ containment: Effective process-tree containment state.
119
+ """
120
+
121
+ returncode: int | None
122
+ stdout: bytes
123
+ blocking_stderr: str
124
+ failure: StepFailure | None = None
125
+ containment: ContainmentState = ContainmentState.CONTAINED
@@ -0,0 +1,130 @@
1
+ """Public host adapter contract, doctor records, and composition skeleton.
2
+
3
+ An adapter package depends on this subpackage alone. It publishes the
4
+ structural types a `sequential_hooks.hosts` entry point must satisfy, the
5
+ doctor record types a loader produces, and `Composer`, the shared response
6
+ composition skeleton a host may subclass instead of implementing `HostAdapter`
7
+ directly.
8
+ """
9
+
10
+ from sequential_hooks.hosts._contract import (
11
+ API_VERSION,
12
+ CONTAINMENT_STATES,
13
+ FAILURE_KINDS,
14
+ ChainControl,
15
+ Diagnostics,
16
+ DoctorLoader,
17
+ FinalResponse,
18
+ HostAdapter,
19
+ HostInputError,
20
+ HostSpec,
21
+ JsonObject,
22
+ JsonValue,
23
+ Step,
24
+ StepFailure,
25
+ StepResult,
26
+ )
27
+ from sequential_hooks.hosts._inspection import (
28
+ deduplicate_source_paths,
29
+ parse_timeout,
30
+ project_layers,
31
+ )
32
+ from sequential_hooks.hosts._json import (
33
+ JsonObjectExpectedError,
34
+ JsonProtocolError,
35
+ load_object,
36
+ load_object_text,
37
+ )
38
+ from sequential_hooks.hosts._records import (
39
+ ConfigSource,
40
+ ContractError,
41
+ Finding,
42
+ FindingSeverity,
43
+ InspectedCommand,
44
+ LaunchSemantics,
45
+ Registration,
46
+ SourceKind,
47
+ WrapperInvocation,
48
+ WrapperStep,
49
+ )
50
+ from sequential_hooks.hosts._skeleton import (
51
+ DIAGNOSTIC_LIMIT_CHARACTERS,
52
+ FINAL_RESPONSE_LIMIT_BYTES,
53
+ TERMINATION_FAILURES,
54
+ TRUNCATION_MARKER,
55
+ UNCONTAINED_WARNING,
56
+ AdapterOutputError,
57
+ Composer,
58
+ CompositionState,
59
+ Contribution,
60
+ NativeResponse,
61
+ Outcome,
62
+ OutputFailureKind,
63
+ UserContribution,
64
+ bounded_contributions,
65
+ bounded_diagnostic,
66
+ continue_after_failure,
67
+ encode_plain_response,
68
+ encode_response,
69
+ labeled,
70
+ optional_string,
71
+ reject_unsupported,
72
+ unsupported_fields,
73
+ )
74
+
75
+ __all__ = [
76
+ 'API_VERSION',
77
+ 'CONTAINMENT_STATES',
78
+ 'DIAGNOSTIC_LIMIT_CHARACTERS',
79
+ 'FAILURE_KINDS',
80
+ 'FINAL_RESPONSE_LIMIT_BYTES',
81
+ 'TERMINATION_FAILURES',
82
+ 'TRUNCATION_MARKER',
83
+ 'UNCONTAINED_WARNING',
84
+ 'AdapterOutputError',
85
+ 'ChainControl',
86
+ 'Composer',
87
+ 'CompositionState',
88
+ 'ConfigSource',
89
+ 'ContractError',
90
+ 'Contribution',
91
+ 'Diagnostics',
92
+ 'DoctorLoader',
93
+ 'FinalResponse',
94
+ 'Finding',
95
+ 'FindingSeverity',
96
+ 'HostAdapter',
97
+ 'HostInputError',
98
+ 'HostSpec',
99
+ 'InspectedCommand',
100
+ 'JsonObject',
101
+ 'JsonObjectExpectedError',
102
+ 'JsonProtocolError',
103
+ 'JsonValue',
104
+ 'LaunchSemantics',
105
+ 'NativeResponse',
106
+ 'Outcome',
107
+ 'OutputFailureKind',
108
+ 'Registration',
109
+ 'SourceKind',
110
+ 'Step',
111
+ 'StepFailure',
112
+ 'StepResult',
113
+ 'UserContribution',
114
+ 'WrapperInvocation',
115
+ 'WrapperStep',
116
+ 'bounded_contributions',
117
+ 'bounded_diagnostic',
118
+ 'continue_after_failure',
119
+ 'deduplicate_source_paths',
120
+ 'encode_plain_response',
121
+ 'encode_response',
122
+ 'labeled',
123
+ 'load_object',
124
+ 'load_object_text',
125
+ 'optional_string',
126
+ 'parse_timeout',
127
+ 'project_layers',
128
+ 'reject_unsupported',
129
+ 'unsupported_fields',
130
+ ]