okstra 0.103.0 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -4,17 +4,22 @@ from __future__ import annotations
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Mapping
|
|
6
6
|
|
|
7
|
-
from okstra_ctl.jsonl import read_jsonl
|
|
7
|
+
from okstra_ctl.jsonl import append_jsonl, read_jsonl
|
|
8
8
|
|
|
9
9
|
from .manager_paths import (
|
|
10
10
|
child_context_path,
|
|
11
11
|
children_json_path,
|
|
12
12
|
directives_jsonl_path,
|
|
13
|
+
events_jsonl_path,
|
|
13
14
|
projects_json_path,
|
|
14
15
|
snapshots_json_path,
|
|
15
16
|
task_manifest_path,
|
|
16
17
|
)
|
|
17
|
-
from .manager_store import ManagerError, _now_iso, _read_json, _read_json_default
|
|
18
|
+
from .manager_store import ManagerError, _now_iso, _read_json, _read_json_default, _write_json
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
WORKER_DISPATCH_BACKEND = "subagent"
|
|
22
|
+
LAUNCH_STATUS_PREPARED = "prepared"
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
def select_child_lead_backend(env: Mapping[str, str]) -> str:
|
|
@@ -135,6 +140,47 @@ def _render_context(
|
|
|
135
140
|
return "\n".join(lines)
|
|
136
141
|
|
|
137
142
|
|
|
143
|
+
def _record_launch_prepared(
|
|
144
|
+
*,
|
|
145
|
+
home: Path,
|
|
146
|
+
manager_id: str,
|
|
147
|
+
task_group: str,
|
|
148
|
+
task_id: str,
|
|
149
|
+
children: dict,
|
|
150
|
+
child: dict,
|
|
151
|
+
packet: dict,
|
|
152
|
+
created_at: str,
|
|
153
|
+
) -> None:
|
|
154
|
+
launch = child.get("launch")
|
|
155
|
+
if not isinstance(launch, dict):
|
|
156
|
+
launch = {}
|
|
157
|
+
launch.update(
|
|
158
|
+
{
|
|
159
|
+
"status": LAUNCH_STATUS_PREPARED,
|
|
160
|
+
"childLeadBackend": packet["backend"],
|
|
161
|
+
"workerDispatchBackend": WORKER_DISPATCH_BACKEND,
|
|
162
|
+
"taskKey": packet["taskKey"],
|
|
163
|
+
"contextPath": packet["contextPath"],
|
|
164
|
+
"preparedAt": created_at,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
child["launch"] = launch
|
|
168
|
+
_write_json(children_json_path(home, manager_id, task_group, task_id), children)
|
|
169
|
+
append_jsonl(
|
|
170
|
+
events_jsonl_path(home, manager_id, task_group, task_id),
|
|
171
|
+
{
|
|
172
|
+
"event": "child-launch-prepared",
|
|
173
|
+
"createdAt": created_at,
|
|
174
|
+
"projectId": packet["projectId"],
|
|
175
|
+
"taskKey": packet["taskKey"],
|
|
176
|
+
"backend": packet["backend"],
|
|
177
|
+
"workerDispatchBackend": WORKER_DISPATCH_BACKEND,
|
|
178
|
+
"contextPath": packet["contextPath"],
|
|
179
|
+
"runArgs": packet["runArgs"],
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
138
184
|
def build_launch_packet(
|
|
139
185
|
home: Path,
|
|
140
186
|
manager_id: str,
|
|
@@ -159,6 +205,7 @@ def build_launch_packet(
|
|
|
159
205
|
_validate_child_task_key(child, target_task_key)
|
|
160
206
|
project_root = _validated_project_root(project)
|
|
161
207
|
backend = select_child_lead_backend(env or {})
|
|
208
|
+
created_at = now or _now_iso()
|
|
162
209
|
context_path = child_context_path(home, manager_id, task_group, task_id, project_id, child_task_id_value)
|
|
163
210
|
context_path.parent.mkdir(parents=True, exist_ok=True)
|
|
164
211
|
context_path.write_text(
|
|
@@ -171,14 +218,14 @@ def build_launch_packet(
|
|
|
171
218
|
),
|
|
172
219
|
encoding="utf-8",
|
|
173
220
|
)
|
|
174
|
-
|
|
221
|
+
packet = {
|
|
175
222
|
"managerId": manager_id,
|
|
176
223
|
"taskGroup": task_group,
|
|
177
224
|
"taskId": task_id,
|
|
178
225
|
"projectId": project_id,
|
|
179
226
|
"taskKey": target_task_key,
|
|
180
227
|
"backend": backend,
|
|
181
|
-
"workerDispatchBackend":
|
|
228
|
+
"workerDispatchBackend": WORKER_DISPATCH_BACKEND,
|
|
182
229
|
"projectRoot": project_root,
|
|
183
230
|
"contextPath": str(context_path),
|
|
184
231
|
"runArgs": [
|
|
@@ -194,5 +241,16 @@ def build_launch_packet(
|
|
|
194
241
|
"--directive",
|
|
195
242
|
f"Read manager child context: {context_path}",
|
|
196
243
|
],
|
|
197
|
-
"createdAt":
|
|
244
|
+
"createdAt": created_at,
|
|
198
245
|
}
|
|
246
|
+
_record_launch_prepared(
|
|
247
|
+
home=home,
|
|
248
|
+
manager_id=manager_id,
|
|
249
|
+
task_group=task_group,
|
|
250
|
+
task_id=task_id,
|
|
251
|
+
children=children,
|
|
252
|
+
child=child,
|
|
253
|
+
packet=packet,
|
|
254
|
+
created_at=created_at,
|
|
255
|
+
)
|
|
256
|
+
return packet
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Pure path computation for okstra manager state."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import hashlib
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
import os
|
|
6
7
|
|
|
@@ -21,9 +22,11 @@ def _require_id(value: str, label: str) -> str:
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _slug_segment(value: str, label: str) -> str:
|
|
24
|
-
|
|
25
|
+
cleaned = _require_id(value, label)
|
|
26
|
+
segment = slugify(cleaned)
|
|
25
27
|
if not segment:
|
|
26
|
-
|
|
28
|
+
digest = hashlib.sha1(cleaned.encode("utf-8")).hexdigest()[:12]
|
|
29
|
+
return f"u-{digest}"
|
|
27
30
|
return segment
|
|
28
31
|
|
|
29
32
|
|
|
@@ -93,6 +96,6 @@ def child_context_path(
|
|
|
93
96
|
project_id: str,
|
|
94
97
|
child_task_id: str,
|
|
95
98
|
) -> Path:
|
|
96
|
-
safe_project =
|
|
97
|
-
safe_task =
|
|
99
|
+
safe_project = _slug_segment(project_id, "project-id")
|
|
100
|
+
safe_task = _slug_segment(child_task_id, "task-id")
|
|
98
101
|
return task_root(home, manager_id, task_group, task_id) / "child-context" / f"{safe_project}-{safe_task}.md"
|
|
@@ -126,9 +126,11 @@ def link_project(
|
|
|
126
126
|
role: str,
|
|
127
127
|
tags: list[str],
|
|
128
128
|
now: str | None = None,
|
|
129
|
-
) -> dict:
|
|
129
|
+
) -> dict:
|
|
130
130
|
load_manager(home, manager_id)
|
|
131
131
|
root = Path(project_root).resolve()
|
|
132
|
+
if not root.is_dir():
|
|
133
|
+
raise ManagerError(f"projectRoot is not a directory: {root}")
|
|
132
134
|
project_json = project_json_path(root)
|
|
133
135
|
if project_json.is_file():
|
|
134
136
|
try:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from okstra_project import TASKS_RELATIVE, parse_task_key, read_task_catalog, read_task_manifest, slugify
|
|
6
|
+
from okstra_project import TASKS_RELATIVE, StateError, parse_task_key, read_task_catalog, read_task_manifest, slugify
|
|
7
7
|
|
|
8
8
|
from .manager_paths import children_json_path, projects_json_path, snapshots_json_path, task_manifest_path
|
|
9
9
|
from .manager_store import _now_iso, _read_json, _read_json_default, _write_json
|
|
@@ -52,9 +52,9 @@ def _resolve_task_root_read_only(project_root: Path, task_key: str) -> Path | No
|
|
|
52
52
|
return None
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def _base_child_snapshot(child: dict) -> dict:
|
|
56
56
|
task_key = str(child.get("taskKey") or "")
|
|
57
|
-
|
|
57
|
+
return {
|
|
58
58
|
"projectId": str(child.get("projectId") or ""),
|
|
59
59
|
"taskGroup": str(child.get("taskGroup") or ""),
|
|
60
60
|
"taskId": str(child.get("taskId") or ""),
|
|
@@ -62,10 +62,18 @@ def _snapshot_child(project_root: Path, child: dict) -> dict:
|
|
|
62
62
|
"exists": False,
|
|
63
63
|
"taskRoot": "",
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _snapshot_child(project_root: Path, child: dict) -> dict:
|
|
68
|
+
base = _base_child_snapshot(child)
|
|
69
|
+
task_key = base["taskKey"]
|
|
70
|
+
try:
|
|
71
|
+
task_dir = _resolve_task_root_read_only(project_root, task_key)
|
|
72
|
+
if task_dir is None:
|
|
73
|
+
return base
|
|
74
|
+
manifest = read_task_manifest(task_dir) or {}
|
|
75
|
+
except StateError as exc:
|
|
76
|
+
return {**base, "error": str(exc)}
|
|
69
77
|
workflow = _workflow(manifest)
|
|
70
78
|
outcome = _phase_outcome(manifest)
|
|
71
79
|
return {
|