cowork-flow 0.0.48 → 0.0.49
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/README.md +16 -1
- package/package.json +6 -3
- package/src/cli.js +6 -0
- package/src/commands/source-refresh.js +40 -0
- package/src/lib/copy-template.js +130 -1
- package/src/lib/host-assets.js +216 -71
- package/template/.codex/hooks.json +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/task.py +0 -2
- package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +12 -0
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +18 -133
- package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +82 -1
- package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +98 -114
- package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
- package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
- package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
- package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
- package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
- package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +23 -4
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
- package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
- package/template/.cowork-flow/scripts/services/task_archive.py +100 -14
- package/template/.cowork-flow/scripts/services/task_context.py +49 -479
- package/template/.cowork-flow/scripts/services/task_creation.py +31 -27
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
- package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
- package/template/.cowork-flow/spec/runtime/index.md +1 -1
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +32 -6
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/skills/batch-execution/scripts/batch_execution.py +111 -629
- package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
- package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
- package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
- package/template/skills/cowork-flow/SKILL.md +3 -0
- package/template/skills/party-mode/scripts/party_actions.py +95 -0
- package/template/skills/party-mode/scripts/party_mode_v2.py +129 -205
- package/template/skills/party-mode/scripts/party_reports.py +177 -0
- package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
- package/template/skills/runtime-health/scripts/doctor.py +204 -15
- package/template/skills/task-review/scripts/review_check.py +135 -2
- package/template/skills/test-first/SKILL.md +18 -3
- package/template/skills/test-first/manifest.json +8 -4
|
@@ -12,7 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
from infra.paths import DIR_ARCHIVE, get_tasks_dir
|
|
13
13
|
from runtime.session_state import clear_task_from_sessions
|
|
14
14
|
from infra.archive_utils import archive_directory_resumable
|
|
15
|
-
from services.
|
|
15
|
+
from services.context_jsonl import read_context_jsonl_entries
|
|
16
16
|
from services.task_repository import TaskRepository, TaskRepositoryError
|
|
17
17
|
from services.task_utils import find_task_by_name
|
|
18
18
|
|
|
@@ -21,13 +21,37 @@ from kernel.task_state import DONE_STATUSES # noqa: F401
|
|
|
21
21
|
ArchiveFinalizer = Callable[[], bool]
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class RollbackIssue:
|
|
26
|
+
"""Best-effort compensation issue that did not replace the primary error."""
|
|
27
|
+
|
|
28
|
+
stage: str
|
|
29
|
+
path: Path
|
|
30
|
+
detail: str
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict[str, str]:
|
|
33
|
+
return {
|
|
34
|
+
"stage": self.stage,
|
|
35
|
+
"path": str(self.path),
|
|
36
|
+
"detail": self.detail,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
24
40
|
class TaskArchiveError(RuntimeError):
|
|
25
41
|
"""Raised when an archive_task action cannot complete safely."""
|
|
26
42
|
|
|
27
|
-
def __init__(
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
code: str,
|
|
46
|
+
path: Path,
|
|
47
|
+
detail: str,
|
|
48
|
+
*,
|
|
49
|
+
rollback_issues: tuple[RollbackIssue, ...] = (),
|
|
50
|
+
) -> None:
|
|
28
51
|
self.code = code
|
|
29
52
|
self.path = path
|
|
30
53
|
self.detail = detail
|
|
54
|
+
self.rollback_issues = tuple(rollback_issues)
|
|
31
55
|
super().__init__(f"{code}: {detail}: {path}")
|
|
32
56
|
|
|
33
57
|
|
|
@@ -160,7 +184,7 @@ class TaskArchiveService:
|
|
|
160
184
|
"archive finalizer failed",
|
|
161
185
|
)
|
|
162
186
|
except Exception as error:
|
|
163
|
-
self._rollback(
|
|
187
|
+
rollback_issues = self._rollback(
|
|
164
188
|
task_dir,
|
|
165
189
|
destination,
|
|
166
190
|
task_data,
|
|
@@ -168,17 +192,22 @@ class TaskArchiveService:
|
|
|
168
192
|
context_snapshots,
|
|
169
193
|
)
|
|
170
194
|
if isinstance(error, TaskArchiveError):
|
|
195
|
+
error.rollback_issues = tuple(
|
|
196
|
+
(*error.rollback_issues, *rollback_issues)
|
|
197
|
+
)
|
|
171
198
|
raise
|
|
172
199
|
if isinstance(error, TaskRepositoryError):
|
|
173
200
|
raise TaskArchiveError(
|
|
174
201
|
"TASK-ARCHIVE-WRITE-001",
|
|
175
202
|
error.path,
|
|
176
203
|
error.detail,
|
|
204
|
+
rollback_issues=rollback_issues,
|
|
177
205
|
) from error
|
|
178
206
|
raise TaskArchiveError(
|
|
179
207
|
"TASK-ARCHIVE-FINALIZE-001",
|
|
180
208
|
destination,
|
|
181
209
|
f"archive finalizer failed: {error}",
|
|
210
|
+
rollback_issues=rollback_issues,
|
|
182
211
|
) from error
|
|
183
212
|
|
|
184
213
|
def _relationship_updates(
|
|
@@ -313,15 +342,23 @@ class TaskArchiveService:
|
|
|
313
342
|
self.repository.save(task_dir, changes)
|
|
314
343
|
applied.append((task_dir, original))
|
|
315
344
|
except TaskRepositoryError as error:
|
|
345
|
+
rollback_issues: list[RollbackIssue] = []
|
|
316
346
|
for task_dir, original in reversed(applied):
|
|
317
347
|
try:
|
|
318
348
|
self.repository.replace(task_dir, original)
|
|
319
|
-
except
|
|
320
|
-
|
|
349
|
+
except Exception as rollback_error:
|
|
350
|
+
rollback_issues.append(
|
|
351
|
+
RollbackIssue(
|
|
352
|
+
"relationship_restore",
|
|
353
|
+
Path(str(getattr(rollback_error, "path", task_dir / "task.json"))),
|
|
354
|
+
str(getattr(rollback_error, "detail", rollback_error)),
|
|
355
|
+
)
|
|
356
|
+
)
|
|
321
357
|
raise TaskArchiveError(
|
|
322
358
|
"TASK-ARCHIVE-RELATIONSHIP-001",
|
|
323
359
|
error.path,
|
|
324
360
|
error.detail,
|
|
361
|
+
rollback_issues=tuple(rollback_issues),
|
|
325
362
|
) from error
|
|
326
363
|
|
|
327
364
|
def _rollback(
|
|
@@ -331,22 +368,71 @@ class TaskArchiveService:
|
|
|
331
368
|
task_data: dict,
|
|
332
369
|
relationship_updates: list[tuple[Path, dict, dict]],
|
|
333
370
|
context_snapshots: dict[str, bytes],
|
|
334
|
-
) ->
|
|
371
|
+
) -> tuple[RollbackIssue, ...]:
|
|
372
|
+
issues: list[RollbackIssue] = []
|
|
335
373
|
for task_dir, original, _ in relationship_updates:
|
|
336
374
|
try:
|
|
337
375
|
self.repository.replace(task_dir, original)
|
|
338
|
-
except
|
|
339
|
-
|
|
376
|
+
except Exception as error:
|
|
377
|
+
issues.append(
|
|
378
|
+
RollbackIssue(
|
|
379
|
+
"relationship_restore",
|
|
380
|
+
Path(str(getattr(error, "path", task_dir / "task.json"))),
|
|
381
|
+
str(getattr(error, "detail", error)),
|
|
382
|
+
)
|
|
383
|
+
)
|
|
340
384
|
|
|
385
|
+
directory_restore_attempted = False
|
|
341
386
|
if destination.is_dir() and not source.exists():
|
|
342
|
-
|
|
387
|
+
directory_restore_attempted = True
|
|
388
|
+
try:
|
|
389
|
+
move_result = archive_directory_resumable(destination, source)
|
|
390
|
+
except Exception as error: # best-effort compensation boundary
|
|
391
|
+
issues.append(
|
|
392
|
+
RollbackIssue(
|
|
393
|
+
"directory_restore",
|
|
394
|
+
source,
|
|
395
|
+
f"failed to restore archived directory: {error}",
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
else:
|
|
399
|
+
if not move_result.ok:
|
|
400
|
+
issues.append(
|
|
401
|
+
RollbackIssue(
|
|
402
|
+
"directory_restore",
|
|
403
|
+
move_result.destination,
|
|
404
|
+
move_result.message,
|
|
405
|
+
)
|
|
406
|
+
)
|
|
343
407
|
if source.is_dir():
|
|
344
408
|
for name, content in context_snapshots.items():
|
|
409
|
+
context_path = source / name
|
|
345
410
|
try:
|
|
346
|
-
|
|
347
|
-
except
|
|
348
|
-
|
|
411
|
+
context_path.write_bytes(content)
|
|
412
|
+
except Exception as error:
|
|
413
|
+
issues.append(
|
|
414
|
+
RollbackIssue(
|
|
415
|
+
"context_restore",
|
|
416
|
+
context_path,
|
|
417
|
+
str(error),
|
|
418
|
+
)
|
|
419
|
+
)
|
|
349
420
|
try:
|
|
350
421
|
self.repository.replace(source, task_data)
|
|
351
|
-
except
|
|
352
|
-
|
|
422
|
+
except Exception as error:
|
|
423
|
+
issues.append(
|
|
424
|
+
RollbackIssue(
|
|
425
|
+
"task_json_restore",
|
|
426
|
+
Path(str(getattr(error, "path", source / "task.json"))),
|
|
427
|
+
str(getattr(error, "detail", error)),
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
elif not source.exists() and not directory_restore_attempted:
|
|
431
|
+
issues.append(
|
|
432
|
+
RollbackIssue(
|
|
433
|
+
"directory_restore",
|
|
434
|
+
source,
|
|
435
|
+
"source task directory was not restored",
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
return tuple(issues)
|