agent-lifecycle-kit 1.50.1__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.
- agent_lifecycle/__init__.py +5 -0
- agent_lifecycle/__main__.py +5 -0
- agent_lifecycle/_version.py +3 -0
- agent_lifecycle/adapter_sessions/__init__.py +41 -0
- agent_lifecycle/adapter_sessions/contracts.py +133 -0
- agent_lifecycle/adapter_sessions/env.py +44 -0
- agent_lifecycle/adapter_sessions/launcher.py +101 -0
- agent_lifecycle/adapter_sessions/process.py +39 -0
- agent_lifecycle/adapter_sessions/redaction.py +30 -0
- agent_lifecycle/adapter_sessions/session_store.py +64 -0
- agent_lifecycle/adapter_sessions/task_intake.py +450 -0
- agent_lifecycle/adapter_sessions/workflow_bridge.py +154 -0
- agent_lifecycle/audit/__init__.py +67 -0
- agent_lifecycle/audit/bug_forensics.py +94 -0
- agent_lifecycle/audit/implementation.py +516 -0
- agent_lifecycle/audit/ownership.py +119 -0
- agent_lifecycle/audit/proof_integrity.py +814 -0
- agent_lifecycle/audit/review_verdict.py +116 -0
- agent_lifecycle/changesets/__init__.py +5 -0
- agent_lifecycle/changesets/git.py +35 -0
- agent_lifecycle/cli/__init__.py +5 -0
- agent_lifecycle/cli/adapter.py +360 -0
- agent_lifecycle/cli/dispatch.py +1220 -0
- agent_lifecycle/cli/followup.py +89 -0
- agent_lifecycle/cli/main.py +41 -0
- agent_lifecycle/cli/metrics_parser.py +49 -0
- agent_lifecycle/cli/parsers.py +618 -0
- agent_lifecycle/cli/policy.py +101 -0
- agent_lifecycle/cli/progress_hooks.py +117 -0
- agent_lifecycle/cli/worktree.py +74 -0
- agent_lifecycle/compiler/__init__.py +17 -0
- agent_lifecycle/compiler/small_model_packets.py +322 -0
- agent_lifecycle/compiler/task_packets.py +229 -0
- agent_lifecycle/context/__init__.py +20 -0
- agent_lifecycle/context/episode_retrieval.py +30 -0
- agent_lifecycle/context/external_memory.py +71 -0
- agent_lifecycle/context/profiles.py +104 -0
- agent_lifecycle/context/rendering.py +226 -0
- agent_lifecycle/contracts/__init__.py +25 -0
- agent_lifecycle/contracts/adapter_contract_schemas.py +130 -0
- agent_lifecycle/contracts/adapter_event_schemas.py +118 -0
- agent_lifecycle/contracts/adapter_session_schemas.py +130 -0
- agent_lifecycle/contracts/adapter_task_schemas.py +90 -0
- agent_lifecycle/contracts/audit_schemas.py +133 -0
- agent_lifecycle/contracts/bug_forensics_schemas.py +400 -0
- agent_lifecycle/contracts/canonical.py +55 -0
- agent_lifecycle/contracts/compatibility.py +242 -0
- agent_lifecycle/contracts/context_model_schemas.py +265 -0
- agent_lifecycle/contracts/core_schemas.py +252 -0
- agent_lifecycle/contracts/cross_check_schemas.py +121 -0
- agent_lifecycle/contracts/errors.py +26 -0
- agent_lifecycle/contracts/evidence_import_schemas.py +152 -0
- agent_lifecycle/contracts/host_capability_schemas.py +64 -0
- agent_lifecycle/contracts/import_dialect_schemas.py +166 -0
- agent_lifecycle/contracts/metric_schemas.py +315 -0
- agent_lifecycle/contracts/paths.py +31 -0
- agent_lifecycle/contracts/plan_contract_schemas.py +148 -0
- agent_lifecycle/contracts/policy_schemas.py +350 -0
- agent_lifecycle/contracts/progress_bridge_schemas.py +114 -0
- agent_lifecycle/contracts/progress_hook_schemas.py +119 -0
- agent_lifecycle/contracts/proof_integrity_schemas.py +240 -0
- agent_lifecycle/contracts/release_contract_schemas.py +476 -0
- agent_lifecycle/contracts/review_mesh_recommendation_schemas.py +64 -0
- agent_lifecycle/contracts/review_mesh_schemas.py +222 -0
- agent_lifecycle/contracts/review_quality_schemas.py +109 -0
- agent_lifecycle/contracts/runner_schemas.py +91 -0
- agent_lifecycle/contracts/runner_worktree_schemas.py +389 -0
- agent_lifecycle/contracts/sandbox_schemas.py +159 -0
- agent_lifecycle/contracts/schema_builders.py +23 -0
- agent_lifecycle/contracts/schemas.py +93 -0
- agent_lifecycle/contracts/status_goal_schemas.py +469 -0
- agent_lifecycle/contracts/task_template_schemas.py +96 -0
- agent_lifecycle/contracts/usage_export_schemas.py +68 -0
- agent_lifecycle/diagnostics/__init__.py +17 -0
- agent_lifecycle/diagnostics/bundles.py +124 -0
- agent_lifecycle/diagnostics/readiness.py +733 -0
- agent_lifecycle/evidence_index/__init__.py +39 -0
- agent_lifecycle/evidence_index/core.py +272 -0
- agent_lifecycle/evidence_index/episode_index.py +329 -0
- agent_lifecycle/evidence_index/external_context.py +276 -0
- agent_lifecycle/followup/__init__.py +23 -0
- agent_lifecycle/followup/records.py +404 -0
- agent_lifecycle/freeze/__init__.py +5 -0
- agent_lifecycle/freeze/locks.py +23 -0
- agent_lifecycle/goal/__init__.py +15 -0
- agent_lifecycle/goal/records.py +244 -0
- agent_lifecycle/goal/view.py +109 -0
- agent_lifecycle/host_protocol/__init__.py +65 -0
- agent_lifecycle/host_protocol/acp_capability.py +195 -0
- agent_lifecycle/host_protocol/capabilities.py +268 -0
- agent_lifecycle/host_protocol/contracts.py +214 -0
- agent_lifecycle/host_protocol/event_capture.py +390 -0
- agent_lifecycle/host_protocol/events.py +196 -0
- agent_lifecycle/host_protocol/inspection.py +194 -0
- agent_lifecycle/host_protocol/inspection_common.py +310 -0
- agent_lifecycle/host_protocol/inspection_cursor.py +164 -0
- agent_lifecycle/host_protocol/inspection_gemini_cli.py +163 -0
- agent_lifecycle/host_protocol/inspection_hermes.py +161 -0
- agent_lifecycle/host_protocol/inspection_kimi_code.py +169 -0
- agent_lifecycle/host_protocol/inspection_opencode.py +147 -0
- agent_lifecycle/host_protocol/inspection_qwen_code.py +143 -0
- agent_lifecycle/host_protocol/receipts.py +58 -0
- agent_lifecycle/host_protocol/scaffold.py +329 -0
- agent_lifecycle/host_protocol/validation.py +229 -0
- agent_lifecycle/imports/__init__.py +76 -0
- agent_lifecycle/imports/agentskills_profile.py +54 -0
- agent_lifecycle/imports/bmad_profile.py +49 -0
- agent_lifecycle/imports/constitution_adr.py +91 -0
- agent_lifecycle/imports/dialect_profiles.py +121 -0
- agent_lifecycle/imports/external_agent.py +28 -0
- agent_lifecycle/imports/external_dialects.py +486 -0
- agent_lifecycle/imports/external_workflow.py +28 -0
- agent_lifecycle/imports/multi_markdown.py +111 -0
- agent_lifecycle/imports/openspec_profile.py +49 -0
- agent_lifecycle/imports/planning.py +536 -0
- agent_lifecycle/imports/spec_kit_profile.py +49 -0
- agent_lifecycle/imports/spec_kitty_profile.py +49 -0
- agent_lifecycle/metrics/__init__.py +60 -0
- agent_lifecycle/metrics/cost_collection.py +315 -0
- agent_lifecycle/metrics/costs.py +203 -0
- agent_lifecycle/metrics/outcome_index.py +330 -0
- agent_lifecycle/metrics/phase_resources.py +208 -0
- agent_lifecycle/metrics/recommendations.py +507 -0
- agent_lifecycle/metrics/regression_signals.py +64 -0
- agent_lifecycle/metrics/usage_export.py +390 -0
- agent_lifecycle/model_routing/__init__.py +19 -0
- agent_lifecycle/model_routing/profiles.py +323 -0
- agent_lifecycle/model_routing/receipts.py +140 -0
- agent_lifecycle/model_routing/resolver.py +566 -0
- agent_lifecycle/neutrality/__init__.py +12 -0
- agent_lifecycle/neutrality/__main__.py +6 -0
- agent_lifecycle/neutrality/authority.py +265 -0
- agent_lifecycle/neutrality/canonical.py +28 -0
- agent_lifecycle/neutrality/cli.py +294 -0
- agent_lifecycle/neutrality/ed25519.py +124 -0
- agent_lifecycle/neutrality/errors.py +89 -0
- agent_lifecycle/neutrality/gate.py +269 -0
- agent_lifecycle/neutrality/paths.py +92 -0
- agent_lifecycle/neutrality/policy.py +76 -0
- agent_lifecycle/neutrality/receipt.py +135 -0
- agent_lifecycle/neutrality/scanner.py +310 -0
- agent_lifecycle/neutrality/test_runner.py +116 -0
- agent_lifecycle/planning/__init__.py +47 -0
- agent_lifecycle/planning/acceptance_markdown.py +159 -0
- agent_lifecycle/planning/completeness.py +362 -0
- agent_lifecycle/planning/continuity.py +255 -0
- agent_lifecycle/planning/templates.py +294 -0
- agent_lifecycle/planning/tiering.py +87 -0
- agent_lifecycle/planning/validation.py +72 -0
- agent_lifecycle/policy/__init__.py +37 -0
- agent_lifecycle/policy/adaptive_lifecycle.py +416 -0
- agent_lifecycle/policy/apply.py +44 -0
- agent_lifecycle/policy/proposals.py +189 -0
- agent_lifecycle/policy/quality_floor.py +144 -0
- agent_lifecycle/policy/runtime_receipts.py +160 -0
- agent_lifecycle/py.typed +1 -0
- agent_lifecycle/quality/__init__.py +89 -0
- agent_lifecycle/quality/bug_forensics.py +727 -0
- agent_lifecycle/quality/bug_forensics_advisor.py +88 -0
- agent_lifecycle/quality/bug_forensics_recipes.py +239 -0
- agent_lifecycle/quality/cross_check.py +437 -0
- agent_lifecycle/quality/failure_classification.py +318 -0
- agent_lifecycle/quality/packs.py +265 -0
- agent_lifecycle/reporting/__init__.py +37 -0
- agent_lifecycle/reporting/change_summary.py +156 -0
- agent_lifecycle/reporting/event_feed.py +101 -0
- agent_lifecycle/reporting/progress_bridge.py +153 -0
- agent_lifecycle/reporting/progress_hooks.py +161 -0
- agent_lifecycle/reporting/progress_terminal.py +95 -0
- agent_lifecycle/reporting/progress_view.py +284 -0
- agent_lifecycle/reporting/status_view.py +125 -0
- agent_lifecycle/reporting/usage_export.py +59 -0
- agent_lifecycle/review/__init__.py +5 -0
- agent_lifecycle/review/validation.py +29 -0
- agent_lifecycle/review_mesh/__init__.py +83 -0
- agent_lifecycle/review_mesh/assignments.py +160 -0
- agent_lifecycle/review_mesh/contracts.py +685 -0
- agent_lifecycle/review_mesh/operator_templates.py +320 -0
- agent_lifecycle/review_mesh/quorum.py +38 -0
- agent_lifecycle/review_mesh/recommendation.py +487 -0
- agent_lifecycle/review_mesh/results.py +98 -0
- agent_lifecycle/review_mesh/synthesis.py +102 -0
- agent_lifecycle/runner/__init__.py +47 -0
- agent_lifecycle/runner/attempt_snapshots.py +244 -0
- agent_lifecycle/runner/core.py +653 -0
- agent_lifecycle/runner/sandbox_receipts.py +628 -0
- agent_lifecycle/specification/__init__.py +23 -0
- agent_lifecycle/specification/completion_check.py +135 -0
- agent_lifecycle/specification/completion_gate.py +501 -0
- agent_lifecycle/specification/completion_signal.py +75 -0
- agent_lifecycle/specification/validation.py +48 -0
- agent_lifecycle/workflow/__init__.py +79 -0
- agent_lifecycle/workflow/artifacts.py +74 -0
- agent_lifecycle/workflow/budget_decisions.py +370 -0
- agent_lifecycle/workflow/budget_policy.py +140 -0
- agent_lifecycle/workflow/budget_receipts.py +70 -0
- agent_lifecycle/workflow/bug_forensics_gates.py +284 -0
- agent_lifecycle/workflow/controller.py +45 -0
- agent_lifecycle/workflow/events.py +50 -0
- agent_lifecycle/workflow/final_proof_integrity.py +74 -0
- agent_lifecycle/workflow/finalization.py +422 -0
- agent_lifecycle/workflow/gates.py +185 -0
- agent_lifecycle/workflow/implementation_audit_gate.py +126 -0
- agent_lifecycle/workflow/leases.py +220 -0
- agent_lifecycle/workflow/lineage.py +108 -0
- agent_lifecycle/workflow/managed_runner.py +167 -0
- agent_lifecycle/workflow/model_usage.py +106 -0
- agent_lifecycle/workflow/next_action.py +86 -0
- agent_lifecycle/workflow/operation_kernel.py +98 -0
- agent_lifecycle/workflow/plan_adoption.py +439 -0
- agent_lifecycle/workflow/query.py +52 -0
- agent_lifecycle/workflow/review_mesh_gate.py +93 -0
- agent_lifecycle/workflow/reviews.py +168 -0
- agent_lifecycle/workflow/run_transitions.py +163 -0
- agent_lifecycle/workflow/sandbox_policy.py +214 -0
- agent_lifecycle/workflow/selectors.py +57 -0
- agent_lifecycle/workflow/state.py +130 -0
- agent_lifecycle/workflow/task_transitions.py +421 -0
- agent_lifecycle/worktree/__init__.py +17 -0
- agent_lifecycle/worktree/isolation.py +407 -0
- agent_lifecycle_kit-1.50.1.dist-info/METADATA +201 -0
- agent_lifecycle_kit-1.50.1.dist-info/RECORD +227 -0
- agent_lifecycle_kit-1.50.1.dist-info/WHEEL +5 -0
- agent_lifecycle_kit-1.50.1.dist-info/entry_points.txt +3 -0
- agent_lifecycle_kit-1.50.1.dist-info/licenses/LICENSE +201 -0
- agent_lifecycle_kit-1.50.1.dist-info/licenses/NOTICE +5 -0
- agent_lifecycle_kit-1.50.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Managed adapter session helpers."""
|
|
2
|
+
|
|
3
|
+
from agent_lifecycle.adapter_sessions.contracts import (
|
|
4
|
+
build_adapter_session_receipt,
|
|
5
|
+
build_launch_receipt,
|
|
6
|
+
build_resume_receipt,
|
|
7
|
+
)
|
|
8
|
+
from agent_lifecycle.adapter_sessions.launcher import launch_from_descriptor
|
|
9
|
+
from agent_lifecycle.adapter_sessions.session_store import (
|
|
10
|
+
create_session,
|
|
11
|
+
load_session,
|
|
12
|
+
session_path,
|
|
13
|
+
update_session,
|
|
14
|
+
)
|
|
15
|
+
from agent_lifecycle.adapter_sessions.task_intake import (
|
|
16
|
+
ADAPTER_TASK_RUN_REQUEST_SCHEMA,
|
|
17
|
+
ADAPTER_TASK_START_RECEIPT_SCHEMA,
|
|
18
|
+
start_adapter_task,
|
|
19
|
+
)
|
|
20
|
+
from agent_lifecycle.adapter_sessions.workflow_bridge import (
|
|
21
|
+
managed_adapter_run,
|
|
22
|
+
promote_session_to_workflow,
|
|
23
|
+
resume_adapter_session,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"build_adapter_session_receipt",
|
|
28
|
+
"build_launch_receipt",
|
|
29
|
+
"build_resume_receipt",
|
|
30
|
+
"create_session",
|
|
31
|
+
"ADAPTER_TASK_RUN_REQUEST_SCHEMA",
|
|
32
|
+
"ADAPTER_TASK_START_RECEIPT_SCHEMA",
|
|
33
|
+
"launch_from_descriptor",
|
|
34
|
+
"load_session",
|
|
35
|
+
"managed_adapter_run",
|
|
36
|
+
"promote_session_to_workflow",
|
|
37
|
+
"resume_adapter_session",
|
|
38
|
+
"session_path",
|
|
39
|
+
"start_adapter_task",
|
|
40
|
+
"update_session",
|
|
41
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Typed receipts for managed adapter sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from agent_lifecycle.contracts import canonical_digest
|
|
8
|
+
|
|
9
|
+
ADAPTER_SESSION_RECEIPT_SCHEMA = "agent-adapter-session-receipt.v1"
|
|
10
|
+
MANAGED_ADAPTER_LAUNCH_RECEIPT_SCHEMA = "agent-managed-adapter-launch-receipt.v1"
|
|
11
|
+
ADAPTER_SESSION_RESUME_RECEIPT_SCHEMA = "agent-adapter-session-resume-receipt.v1"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_adapter_session_receipt(
|
|
15
|
+
*,
|
|
16
|
+
status: str,
|
|
17
|
+
session_id: str,
|
|
18
|
+
adapter_id: str,
|
|
19
|
+
mode: str,
|
|
20
|
+
launch_profile: dict[str, Any],
|
|
21
|
+
state_identity: dict[str, Any] | None = None,
|
|
22
|
+
managed_workflow_proof: dict[str, Any] | None = None,
|
|
23
|
+
progress_hook_default: str = "off",
|
|
24
|
+
host_launch_started: bool = False,
|
|
25
|
+
state_written: bool = True,
|
|
26
|
+
blockers: list[dict[str, Any]] | None = None,
|
|
27
|
+
next_action: dict[str, Any] | None = None,
|
|
28
|
+
launch_receipt: dict[str, Any] | None = None,
|
|
29
|
+
) -> dict[str, Any]:
|
|
30
|
+
managed_workflow = managed_workflow_proof is not None
|
|
31
|
+
body = {
|
|
32
|
+
"schemaVersion": ADAPTER_SESSION_RECEIPT_SCHEMA,
|
|
33
|
+
"status": status,
|
|
34
|
+
"sessionId": session_id,
|
|
35
|
+
"adapterId": adapter_id,
|
|
36
|
+
"mode": mode,
|
|
37
|
+
"managedWorkflow": managed_workflow,
|
|
38
|
+
"lifecycleCoverageClaimed": managed_workflow and status not in {"WAITING_FOR_TASK", "BLOCKED", "UNMANAGED"},
|
|
39
|
+
"launchProfile": _profile_summary(launch_profile),
|
|
40
|
+
"stateIdentity": state_identity,
|
|
41
|
+
"managedWorkflowProof": managed_workflow_proof,
|
|
42
|
+
"progressHookDefault": progress_hook_default,
|
|
43
|
+
"hostLaunchStarted": host_launch_started,
|
|
44
|
+
"modelCallsStarted": False,
|
|
45
|
+
"stateWritten": state_written,
|
|
46
|
+
"secretsWritten": False,
|
|
47
|
+
"nativeConfigWritten": False,
|
|
48
|
+
"blockers": blockers or [],
|
|
49
|
+
"nextAction": next_action,
|
|
50
|
+
"launchReceipt": launch_receipt,
|
|
51
|
+
"productionPromotionClaimed": False,
|
|
52
|
+
}
|
|
53
|
+
return {**body, "receiptDigest": canonical_digest(body)}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_launch_receipt(
|
|
57
|
+
*,
|
|
58
|
+
status: str,
|
|
59
|
+
adapter_id: str,
|
|
60
|
+
session_id: str,
|
|
61
|
+
launch_mode: str,
|
|
62
|
+
argv: list[str],
|
|
63
|
+
timeout_seconds: float,
|
|
64
|
+
env: dict[str, Any],
|
|
65
|
+
exit_code: int | None,
|
|
66
|
+
timed_out: bool,
|
|
67
|
+
cancelled: bool = False,
|
|
68
|
+
stdout_tail: str = "",
|
|
69
|
+
stderr_tail: str = "",
|
|
70
|
+
host_launch_started: bool = False,
|
|
71
|
+
blockers: list[dict[str, Any]] | None = None,
|
|
72
|
+
) -> dict[str, Any]:
|
|
73
|
+
body = {
|
|
74
|
+
"schemaVersion": MANAGED_ADAPTER_LAUNCH_RECEIPT_SCHEMA,
|
|
75
|
+
"status": status,
|
|
76
|
+
"adapterId": adapter_id,
|
|
77
|
+
"sessionId": session_id,
|
|
78
|
+
"launchMode": launch_mode,
|
|
79
|
+
"argv": argv,
|
|
80
|
+
"shell": False,
|
|
81
|
+
"timeoutSeconds": timeout_seconds,
|
|
82
|
+
"env": env,
|
|
83
|
+
"exitCode": exit_code,
|
|
84
|
+
"timedOut": timed_out,
|
|
85
|
+
"cancelled": cancelled,
|
|
86
|
+
"stdout": {"tail": stdout_tail[-2000:], "redacted": True},
|
|
87
|
+
"stderr": {"tail": stderr_tail[-2000:], "redacted": True},
|
|
88
|
+
"hostLaunchStarted": host_launch_started,
|
|
89
|
+
"modelCallsStarted": False,
|
|
90
|
+
"secretsWritten": False,
|
|
91
|
+
"nativeConfigWritten": False,
|
|
92
|
+
"blockers": blockers or [],
|
|
93
|
+
"productionPromotionClaimed": False,
|
|
94
|
+
}
|
|
95
|
+
return {**body, "receiptDigest": canonical_digest(body)}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_resume_receipt(
|
|
99
|
+
*,
|
|
100
|
+
session_id: str,
|
|
101
|
+
adapter_id: str,
|
|
102
|
+
expected_identity: dict[str, Any],
|
|
103
|
+
actual_identity: dict[str, Any],
|
|
104
|
+
blockers: list[dict[str, Any]] | None = None,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
items = blockers or []
|
|
107
|
+
lineage_status = "FAIL" if items else ("PASS" if actual_identity else "NOT_BOUND")
|
|
108
|
+
status = "BLOCKED" if items else ("PASS" if actual_identity else "UNMANAGED")
|
|
109
|
+
body = {
|
|
110
|
+
"schemaVersion": ADAPTER_SESSION_RESUME_RECEIPT_SCHEMA,
|
|
111
|
+
"status": status,
|
|
112
|
+
"sessionId": session_id,
|
|
113
|
+
"adapterId": adapter_id,
|
|
114
|
+
"lineageStatus": lineage_status,
|
|
115
|
+
"expectedIdentity": expected_identity,
|
|
116
|
+
"actualIdentity": actual_identity,
|
|
117
|
+
"managedWorkflow": not items and bool(actual_identity),
|
|
118
|
+
"lifecycleCoverageClaimed": not items and bool(actual_identity),
|
|
119
|
+
"blockers": items,
|
|
120
|
+
"productionPromotionClaimed": False,
|
|
121
|
+
}
|
|
122
|
+
return {**body, "receiptDigest": canonical_digest(body)}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _profile_summary(profile: dict[str, Any]) -> dict[str, Any]:
|
|
126
|
+
return {
|
|
127
|
+
"status": profile.get("status"),
|
|
128
|
+
"reason": profile.get("reason"),
|
|
129
|
+
"source": "adapter.descriptor.json",
|
|
130
|
+
"shell": False,
|
|
131
|
+
"writesNativeConfig": bool(profile.get("writesNativeConfig", False)),
|
|
132
|
+
"promptInjectionDefault": bool(profile.get("promptInjectionDefault", False)),
|
|
133
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Allowlisted environment resolution for managed adapter launches."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from agent_lifecycle.contracts import LifecycleError, read_json_object
|
|
11
|
+
from agent_lifecycle.adapter_sessions.redaction import redact_env_names
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_launch_env(
|
|
15
|
+
profile: dict[str, Any],
|
|
16
|
+
*,
|
|
17
|
+
policy_path: Path | None = None,
|
|
18
|
+
process_env: dict[str, str] | None = None,
|
|
19
|
+
) -> tuple[dict[str, str], dict[str, Any]]:
|
|
20
|
+
"""Return only allowlisted environment values and a redacted receipt view."""
|
|
21
|
+
|
|
22
|
+
env_policy = profile.get("env", {}) if isinstance(profile.get("env"), dict) else {}
|
|
23
|
+
names = set(_strings(env_policy.get("allow")))
|
|
24
|
+
patterns = set(_strings(env_policy.get("allowPatterns")))
|
|
25
|
+
if policy_path is not None:
|
|
26
|
+
if not env_policy.get("projectPolicyAllowed", False):
|
|
27
|
+
raise LifecycleError("adapter-env-policy-not-allowed", "descriptor does not allow project env policy extension")
|
|
28
|
+
policy = read_json_object(policy_path, label="adapter env policy")
|
|
29
|
+
names.update(_strings(policy.get("allow")))
|
|
30
|
+
patterns.update(_strings(policy.get("allowPatterns")))
|
|
31
|
+
source = dict(os.environ if process_env is None else process_env)
|
|
32
|
+
selected: dict[str, str] = {}
|
|
33
|
+
for name, value in source.items():
|
|
34
|
+
if name in names or any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns):
|
|
35
|
+
selected[name] = value
|
|
36
|
+
return selected, redact_env_names(list(selected))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _strings(value: Any) -> list[str]:
|
|
40
|
+
if value is None:
|
|
41
|
+
return []
|
|
42
|
+
if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value):
|
|
43
|
+
raise LifecycleError("adapter-env-policy-invalid", "env policy entries must be non-empty strings")
|
|
44
|
+
return value
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Descriptor-driven managed adapter launcher."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agent_lifecycle.adapter_sessions.contracts import build_launch_receipt
|
|
9
|
+
from agent_lifecycle.adapter_sessions.env import resolve_launch_env
|
|
10
|
+
from agent_lifecycle.adapter_sessions.process import run_process
|
|
11
|
+
from agent_lifecycle.contracts import LifecycleError, read_json_object
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_adapter_descriptor(adapter_id: str, descriptor_path: Path | None = None) -> tuple[Path, dict[str, Any]]:
|
|
15
|
+
path = descriptor_path or Path("adapters") / adapter_id / "adapter.descriptor.json"
|
|
16
|
+
descriptor = read_json_object(path, label="adapter descriptor")
|
|
17
|
+
if descriptor.get("adapterId") != adapter_id:
|
|
18
|
+
raise LifecycleError("adapter-descriptor-id-mismatch", "descriptor adapterId does not match requested adapter")
|
|
19
|
+
return path, descriptor
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def managed_launch_profile(descriptor: dict[str, Any]) -> dict[str, Any]:
|
|
23
|
+
profile = descriptor.get("managedLaunch")
|
|
24
|
+
if not isinstance(profile, dict):
|
|
25
|
+
raise LifecycleError("adapter-managed-launch-missing", "adapter descriptor must declare managedLaunch")
|
|
26
|
+
return profile
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def launch_from_descriptor(
|
|
30
|
+
*,
|
|
31
|
+
descriptor: dict[str, Any],
|
|
32
|
+
session_id: str,
|
|
33
|
+
launch_mode: str,
|
|
34
|
+
task_id: str | None = None,
|
|
35
|
+
state_path: Path | None = None,
|
|
36
|
+
policy_path: Path | None = None,
|
|
37
|
+
process_env: dict[str, str] | None = None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
profile = managed_launch_profile(descriptor)
|
|
40
|
+
adapter_id = descriptor["adapterId"]
|
|
41
|
+
timeout_seconds = float(profile.get("timeoutSeconds", 30.0))
|
|
42
|
+
status = profile.get("status")
|
|
43
|
+
if status != "SUPPORTED":
|
|
44
|
+
reason = profile.get("reason") or f"managed launch profile is {status}"
|
|
45
|
+
return build_launch_receipt(
|
|
46
|
+
status="BLOCKED",
|
|
47
|
+
adapter_id=adapter_id,
|
|
48
|
+
session_id=session_id,
|
|
49
|
+
launch_mode=launch_mode,
|
|
50
|
+
argv=[],
|
|
51
|
+
timeout_seconds=timeout_seconds,
|
|
52
|
+
env={"includedNames": [], "valuesRedacted": True, "secretValuesStored": False},
|
|
53
|
+
exit_code=None,
|
|
54
|
+
timed_out=False,
|
|
55
|
+
blockers=[{"code": "adapter-managed-launch-unsupported", "profileStatus": status, "reason": reason}],
|
|
56
|
+
)
|
|
57
|
+
argv = _resolve_argv(profile, launch_mode=launch_mode, task_id=task_id, state_path=state_path)
|
|
58
|
+
env, env_receipt = resolve_launch_env(profile, policy_path=policy_path, process_env=process_env)
|
|
59
|
+
result = run_process(argv, env=env, timeout_seconds=timeout_seconds)
|
|
60
|
+
return build_launch_receipt(
|
|
61
|
+
status=result["status"],
|
|
62
|
+
adapter_id=adapter_id,
|
|
63
|
+
session_id=session_id,
|
|
64
|
+
launch_mode=launch_mode,
|
|
65
|
+
argv=argv,
|
|
66
|
+
timeout_seconds=timeout_seconds,
|
|
67
|
+
env=env_receipt,
|
|
68
|
+
exit_code=result["exitCode"],
|
|
69
|
+
timed_out=result["timedOut"],
|
|
70
|
+
stdout_tail=result["stdoutTail"],
|
|
71
|
+
stderr_tail=result["stderrTail"],
|
|
72
|
+
host_launch_started=True,
|
|
73
|
+
blockers=result["blockers"],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resolve_argv(
|
|
78
|
+
profile: dict[str, Any],
|
|
79
|
+
*,
|
|
80
|
+
launch_mode: str,
|
|
81
|
+
task_id: str | None,
|
|
82
|
+
state_path: Path | None,
|
|
83
|
+
) -> list[str]:
|
|
84
|
+
templates = profile.get("argvTemplates")
|
|
85
|
+
if not isinstance(templates, dict):
|
|
86
|
+
raise LifecycleError("adapter-managed-launch-argv-missing", "managed launch profile needs argvTemplates")
|
|
87
|
+
raw = templates.get(launch_mode)
|
|
88
|
+
if not isinstance(raw, list) or not all(isinstance(item, str) and item for item in raw):
|
|
89
|
+
raise LifecycleError("adapter-managed-launch-argv-invalid", f"argv template missing for {launch_mode}")
|
|
90
|
+
values = {
|
|
91
|
+
"taskId": task_id or "",
|
|
92
|
+
"state": state_path.as_posix() if state_path else "",
|
|
93
|
+
}
|
|
94
|
+
return [_render_template(item, values) for item in raw]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _render_template(value: str, values: dict[str, str]) -> str:
|
|
98
|
+
rendered = value
|
|
99
|
+
for key, replacement in values.items():
|
|
100
|
+
rendered = rendered.replace("{{" + key + "}}", replacement)
|
|
101
|
+
return rendered
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Secure process execution for managed adapter sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agent_lifecycle.adapter_sessions.redaction import redact_text
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_process(argv: list[str], *, env: dict[str, str], timeout_seconds: float) -> dict[str, Any]:
|
|
12
|
+
try:
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
argv,
|
|
15
|
+
shell=False,
|
|
16
|
+
env=env,
|
|
17
|
+
text=True,
|
|
18
|
+
stdout=subprocess.PIPE,
|
|
19
|
+
stderr=subprocess.PIPE,
|
|
20
|
+
timeout=timeout_seconds,
|
|
21
|
+
check=False,
|
|
22
|
+
)
|
|
23
|
+
return {
|
|
24
|
+
"status": "PASS" if result.returncode == 0 else "FAIL",
|
|
25
|
+
"exitCode": result.returncode,
|
|
26
|
+
"timedOut": False,
|
|
27
|
+
"stdoutTail": redact_text(result.stdout[-2000:]),
|
|
28
|
+
"stderrTail": redact_text(result.stderr[-2000:]),
|
|
29
|
+
"blockers": [] if result.returncode == 0 else [{"code": "adapter-process-nonzero-exit", "exitCode": result.returncode}],
|
|
30
|
+
}
|
|
31
|
+
except subprocess.TimeoutExpired as exc:
|
|
32
|
+
return {
|
|
33
|
+
"status": "FAIL",
|
|
34
|
+
"exitCode": None,
|
|
35
|
+
"timedOut": True,
|
|
36
|
+
"stdoutTail": redact_text((exc.stdout or "")[-2000:] if isinstance(exc.stdout, str) else ""),
|
|
37
|
+
"stderrTail": redact_text((exc.stderr or "")[-2000:] if isinstance(exc.stderr, str) else ""),
|
|
38
|
+
"blockers": [{"code": "adapter-process-timeout", "timeoutSeconds": timeout_seconds}],
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Redaction helpers for adapter session receipts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
SENSITIVE_MARKERS = ("KEY", "TOKEN", "SECRET", "PASSWORD", "CREDENTIAL")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def redact_env_names(names: list[str]) -> dict[str, object]:
|
|
9
|
+
return {
|
|
10
|
+
"includedNames": sorted(set(names)),
|
|
11
|
+
"valuesRedacted": True,
|
|
12
|
+
"secretValuesStored": False,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def redact_text(value: str) -> str:
|
|
17
|
+
text = value
|
|
18
|
+
for marker in SENSITIVE_MARKERS:
|
|
19
|
+
text = _redact_marker(text, marker)
|
|
20
|
+
return text
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _redact_marker(text: str, marker: str) -> str:
|
|
24
|
+
words = []
|
|
25
|
+
for word in text.split():
|
|
26
|
+
if marker in word.upper() and "=" in word:
|
|
27
|
+
words.append(word.split("=", 1)[0] + "=<redacted>")
|
|
28
|
+
else:
|
|
29
|
+
words.append(word)
|
|
30
|
+
return " ".join(words)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Filesystem store for managed adapter sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from agent_lifecycle.contracts import LifecycleError, canonical_bytes, read_json_object, write_json_create
|
|
10
|
+
|
|
11
|
+
DEFAULT_SESSION_ROOT = ".alk/adapter-sessions"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def session_path(session_id: str, *, session_root: Path | None = None) -> Path:
|
|
15
|
+
if not session_id or "/" in session_id or "\\" in session_id or session_id in {".", ".."}:
|
|
16
|
+
raise LifecycleError("adapter-session-id-invalid", "session id is invalid")
|
|
17
|
+
root = session_root or Path(DEFAULT_SESSION_ROOT)
|
|
18
|
+
path = root / f"{session_id}.json"
|
|
19
|
+
resolved_root = root.resolve()
|
|
20
|
+
resolved_path = path.resolve()
|
|
21
|
+
if not resolved_path.is_relative_to(resolved_root):
|
|
22
|
+
raise LifecycleError("adapter-session-path-escape", "session path escapes session root")
|
|
23
|
+
return path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_session(
|
|
27
|
+
*,
|
|
28
|
+
adapter_id: str,
|
|
29
|
+
mode: str,
|
|
30
|
+
status: str,
|
|
31
|
+
launch_profile: dict[str, Any],
|
|
32
|
+
session_root: Path | None = None,
|
|
33
|
+
state_identity: dict[str, Any] | None = None,
|
|
34
|
+
managed_workflow_proof: dict[str, Any] | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
session_id = uuid.uuid4().hex
|
|
37
|
+
session = {
|
|
38
|
+
"schemaVersion": "agent-adapter-session-state.v1",
|
|
39
|
+
"sessionId": session_id,
|
|
40
|
+
"adapterId": adapter_id,
|
|
41
|
+
"mode": mode,
|
|
42
|
+
"status": status,
|
|
43
|
+
"launchProfile": {
|
|
44
|
+
"status": launch_profile.get("status"),
|
|
45
|
+
"reason": launch_profile.get("reason"),
|
|
46
|
+
},
|
|
47
|
+
"stateIdentity": state_identity,
|
|
48
|
+
"managedWorkflowProof": managed_workflow_proof,
|
|
49
|
+
"secretValuesStored": False,
|
|
50
|
+
"nativeConfigWritten": False,
|
|
51
|
+
}
|
|
52
|
+
write_json_create(session_path(session_id, session_root=session_root), session)
|
|
53
|
+
return session
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_session(session_id: str, *, session_root: Path | None = None) -> dict[str, Any]:
|
|
57
|
+
return read_json_object(session_path(session_id, session_root=session_root), label="adapter session")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def update_session(session: dict[str, Any], *, session_root: Path | None = None) -> dict[str, Any]:
|
|
61
|
+
path = session_path(session["sessionId"], session_root=session_root)
|
|
62
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
path.write_bytes(canonical_bytes(session) + b"\n")
|
|
64
|
+
return session
|