qlogicagent 2.11.1 → 2.11.3

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 (42) hide show
  1. package/dist/cli.js +290 -289
  2. package/dist/index.js +292 -291
  3. package/dist/pet-contracts.js +1 -1
  4. package/dist/protocol.js +1 -1
  5. package/dist/types/cli/handlers/session-handler.d.ts +3 -3
  6. package/dist/types/cli/media-runtime-facade.d.ts +8 -0
  7. package/dist/types/cli/pet-runtime.d.ts +1 -3
  8. package/dist/types/cli/session-query-service.d.ts +1 -0
  9. package/dist/types/protocol/wire/acp-protocol.d.ts +6 -0
  10. package/dist/types/protocol/wire/gateway-rpc.d.ts +2 -0
  11. package/dist/types/protocol/wire/pet-contracts.d.ts +3 -2
  12. package/dist/types/runtime/infra/acp-detector.d.ts +21 -0
  13. package/dist/types/runtime/infra/project-store.d.ts +1 -0
  14. package/dist/types/runtime/pet/hatch-pet-runner.d.ts +18 -0
  15. package/dist/types/runtime/pet/index.d.ts +12 -5
  16. package/dist/types/runtime/pet/pet-file-loader.d.ts +1 -1
  17. package/dist/types/runtime/pet/pet-growth-engine.d.ts +2 -58
  18. package/dist/types/runtime/pet/pet-profile-service.d.ts +5 -7
  19. package/dist/types/runtime/pet/pet-reaction-engine.d.ts +11 -0
  20. package/dist/types/runtime/pet/pet-soul-service.d.ts +2 -2
  21. package/dist/types/runtime/pet/pet-types.d.ts +1 -32
  22. package/dist/types/runtime/pet/petdex-asset.d.ts +3 -109
  23. package/dist/types/runtime/pet/petdex-forge-service.d.ts +3 -94
  24. package/dist/types/runtime/prompt/fresh-workspace-evidence.d.ts +1 -0
  25. package/dist/types/runtime/session/session-locator.d.ts +2 -0
  26. package/dist/types/runtime/session/session-persistence.d.ts +17 -3
  27. package/dist/types/skills/tools/petdex-create-tool.d.ts +2 -16
  28. package/dist/vendor/hatch-pet/LICENSE.txt +201 -0
  29. package/dist/vendor/hatch-pet/NOTICE.md +25 -0
  30. package/dist/vendor/hatch-pet/references/animation-rows.md +29 -0
  31. package/dist/vendor/hatch-pet/references/codex-pet-contract.md +35 -0
  32. package/dist/vendor/hatch-pet/references/qa-rubric.md +66 -0
  33. package/dist/vendor/hatch-pet/scripts/compose_atlas.py +169 -0
  34. package/dist/vendor/hatch-pet/scripts/derive_running_left_from_running_right.py +150 -0
  35. package/dist/vendor/hatch-pet/scripts/extract_strip_frames.py +408 -0
  36. package/dist/vendor/hatch-pet/scripts/inspect_frames.py +256 -0
  37. package/dist/vendor/hatch-pet/scripts/make_contact_sheet.py +96 -0
  38. package/dist/vendor/hatch-pet/scripts/prepare_pet_run.py +830 -0
  39. package/dist/vendor/hatch-pet/scripts/render_animation_previews.py +78 -0
  40. package/dist/vendor/hatch-pet/scripts/validate_atlas.py +157 -0
  41. package/package.json +5 -3
  42. package/dist/types/runtime/pet/pet-reaction-service.d.ts +0 -33
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+ """Compose or normalize a Codex pet spritesheet atlas."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+
9
+ from PIL import Image
10
+
11
+ COLUMNS = 8
12
+ ROWS = 9
13
+ CELL_WIDTH = 192
14
+ CELL_HEIGHT = 208
15
+ ATLAS_WIDTH = COLUMNS * CELL_WIDTH
16
+ ATLAS_HEIGHT = ROWS * CELL_HEIGHT
17
+ ATLAS_ASPECT_RATIO = ATLAS_WIDTH / ATLAS_HEIGHT
18
+ ROW_SPECS = [
19
+ ("idle", 0, 6),
20
+ ("running-right", 1, 8),
21
+ ("running-left", 2, 8),
22
+ ("waving", 3, 4),
23
+ ("jumping", 4, 5),
24
+ ("failed", 5, 8),
25
+ ("waiting", 6, 6),
26
+ ("running", 7, 6),
27
+ ("review", 8, 6),
28
+ ]
29
+ IMAGE_SUFFIXES = {".png", ".webp", ".jpg", ".jpeg"}
30
+
31
+
32
+ def image_files(path: Path) -> list[Path]:
33
+ return sorted(p for p in path.iterdir() if p.suffix.lower() in IMAGE_SUFFIXES)
34
+
35
+
36
+ def find_row_frames(root: Path, state: str, row_index: int) -> list[Path]:
37
+ candidates = [
38
+ root / state,
39
+ root / f"row-{row_index}",
40
+ root / f"row{row_index}",
41
+ root / f"{row_index}-{state}",
42
+ ]
43
+ for candidate in candidates:
44
+ if candidate.is_dir():
45
+ files = image_files(candidate)
46
+ if files:
47
+ return files
48
+ globs = [
49
+ f"{state}_*",
50
+ f"{state}-*",
51
+ f"row{row_index}_*",
52
+ f"row-{row_index}-*",
53
+ ]
54
+ files: list[Path] = []
55
+ for pattern in globs:
56
+ files.extend(p for p in root.glob(pattern) if p.suffix.lower() in IMAGE_SUFFIXES)
57
+ return sorted(set(files))
58
+
59
+
60
+ def paste_centered(atlas: Image.Image, source: Image.Image, row: int, column: int) -> None:
61
+ frame = source.convert("RGBA")
62
+ if frame.size != (CELL_WIDTH, CELL_HEIGHT):
63
+ frame.thumbnail((CELL_WIDTH, CELL_HEIGHT), Image.Resampling.LANCZOS)
64
+ left = column * CELL_WIDTH + (CELL_WIDTH - frame.width) // 2
65
+ top = row * CELL_HEIGHT + (CELL_HEIGHT - frame.height) // 2
66
+ atlas.alpha_composite(frame, (left, top))
67
+
68
+
69
+ def compose_from_source_atlas(path: Path, resize_source: bool) -> Image.Image:
70
+ with Image.open(path) as opened:
71
+ source = opened.convert("RGBA")
72
+ if source.size != (ATLAS_WIDTH, ATLAS_HEIGHT):
73
+ if not resize_source:
74
+ raise SystemExit(
75
+ f"source atlas must be {ATLAS_WIDTH}x{ATLAS_HEIGHT}; got {source.width}x{source.height}"
76
+ )
77
+ source_ratio = source.width / source.height
78
+ if abs(source_ratio - ATLAS_ASPECT_RATIO) > 0.02:
79
+ raise SystemExit(
80
+ "refusing to resize source atlas because its aspect ratio does not match "
81
+ f"the Codex atlas ratio {ATLAS_ASPECT_RATIO:.3f}; got {source_ratio:.3f}. "
82
+ "Generate exact atlas dimensions or use --frames-root."
83
+ )
84
+ source = source.resize((ATLAS_WIDTH, ATLAS_HEIGHT), Image.Resampling.LANCZOS)
85
+
86
+ atlas = Image.new("RGBA", (ATLAS_WIDTH, ATLAS_HEIGHT), (0, 0, 0, 0))
87
+ for _state, row, frame_count in ROW_SPECS:
88
+ for column in range(frame_count):
89
+ left = column * CELL_WIDTH
90
+ top = row * CELL_HEIGHT
91
+ cell = source.crop((left, top, left + CELL_WIDTH, top + CELL_HEIGHT))
92
+ atlas.alpha_composite(cell, (left, top))
93
+ return atlas
94
+
95
+
96
+ def compose_from_frames(root: Path) -> Image.Image:
97
+ atlas = Image.new("RGBA", (ATLAS_WIDTH, ATLAS_HEIGHT), (0, 0, 0, 0))
98
+ for state, row, frame_count in ROW_SPECS:
99
+ files = find_row_frames(root, state, row)
100
+ if len(files) < frame_count:
101
+ raise SystemExit(
102
+ f"{state} row needs {frame_count} frames, found {len(files)} under {root}"
103
+ )
104
+ for column, frame_path in enumerate(files[:frame_count]):
105
+ with Image.open(frame_path) as frame:
106
+ paste_centered(atlas, frame, row, column)
107
+ return atlas
108
+
109
+
110
+ def clear_transparent_rgb(image: Image.Image) -> Image.Image:
111
+ rgba = image.convert("RGBA")
112
+ data = bytearray(rgba.tobytes())
113
+ for index in range(0, len(data), 4):
114
+ if data[index + 3] == 0:
115
+ data[index] = 0
116
+ data[index + 1] = 0
117
+ data[index + 2] = 0
118
+ return Image.frombytes("RGBA", rgba.size, bytes(data))
119
+
120
+
121
+ def save_outputs(atlas: Image.Image, output: Path, webp_output: Path | None) -> None:
122
+ atlas = clear_transparent_rgb(atlas)
123
+ output.parent.mkdir(parents=True, exist_ok=True)
124
+ atlas.save(output)
125
+ if webp_output is not None:
126
+ webp_output.parent.mkdir(parents=True, exist_ok=True)
127
+ atlas.save(
128
+ webp_output,
129
+ format="WEBP",
130
+ lossless=True,
131
+ quality=100,
132
+ method=6,
133
+ exact=True,
134
+ )
135
+
136
+
137
+ def main() -> None:
138
+ parser = argparse.ArgumentParser(description=__doc__)
139
+ source = parser.add_mutually_exclusive_group(required=True)
140
+ source.add_argument("--source-atlas")
141
+ source.add_argument("--frames-root")
142
+ parser.add_argument("--output", required=True)
143
+ parser.add_argument("--webp-output")
144
+ parser.add_argument(
145
+ "--resize-source",
146
+ action="store_true",
147
+ help="Resize a lower-resolution source atlas only when it already has the Codex atlas aspect ratio.",
148
+ )
149
+ args = parser.parse_args()
150
+
151
+ if args.source_atlas:
152
+ atlas = compose_from_source_atlas(
153
+ Path(args.source_atlas).expanduser().resolve(), args.resize_source
154
+ )
155
+ else:
156
+ atlas = compose_from_frames(Path(args.frames_root).expanduser().resolve())
157
+
158
+ save_outputs(
159
+ atlas,
160
+ Path(args.output).expanduser().resolve(),
161
+ Path(args.webp_output).expanduser().resolve() if args.webp_output else None,
162
+ )
163
+ print(f"wrote {Path(args.output).expanduser().resolve()}")
164
+ if args.webp_output:
165
+ print(f"wrote {Path(args.webp_output).expanduser().resolve()}")
166
+
167
+
168
+ if __name__ == "__main__":
169
+ main()
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """Conditionally derive running-left by mirroring the approved running-right strip."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+
11
+ from PIL import Image, ImageOps
12
+
13
+ RUNNING_FRAME_COUNT = 8
14
+
15
+
16
+ def load_manifest(run_dir: Path) -> dict[str, object]:
17
+ path = run_dir / "imagegen-jobs.json"
18
+ if not path.exists():
19
+ raise SystemExit(f"job manifest not found: {path}")
20
+ return json.loads(path.read_text(encoding="utf-8"))
21
+
22
+
23
+ def job_list(manifest: dict[str, object]) -> list[dict[str, object]]:
24
+ jobs = manifest.get("jobs")
25
+ if not isinstance(jobs, list):
26
+ raise SystemExit("invalid imagegen-jobs.json: jobs must be a list")
27
+ return [job for job in jobs if isinstance(job, dict)]
28
+
29
+
30
+ def find_job(manifest: dict[str, object], job_id: str) -> dict[str, object]:
31
+ for job in job_list(manifest):
32
+ if job.get("id") == job_id:
33
+ return job
34
+ raise SystemExit(f"unknown job id: {job_id}")
35
+
36
+
37
+ def image_metadata(path: Path) -> dict[str, object]:
38
+ with Image.open(path) as image:
39
+ image.verify()
40
+ with Image.open(path) as image:
41
+ return {
42
+ "width": image.width,
43
+ "height": image.height,
44
+ "mode": image.mode,
45
+ "format": image.format,
46
+ }
47
+
48
+
49
+ def manifest_relative(path: Path, run_dir: Path) -> str:
50
+ return str(path.resolve().relative_to(run_dir.resolve()))
51
+
52
+
53
+ def mirror_strip_preserving_frame_order(
54
+ source: Image.Image,
55
+ frame_count: int = RUNNING_FRAME_COUNT,
56
+ ) -> Image.Image:
57
+ rgba = source.convert("RGBA")
58
+ mirrored = Image.new("RGBA", rgba.size, (0, 0, 0, 0))
59
+ slot_width = rgba.width / frame_count
60
+ for index in range(frame_count):
61
+ left = round(index * slot_width)
62
+ right = round((index + 1) * slot_width)
63
+ mirrored.alpha_composite(
64
+ ImageOps.mirror(rgba.crop((left, 0, right, rgba.height))),
65
+ (left, 0),
66
+ )
67
+ return mirrored
68
+
69
+
70
+ def main() -> None:
71
+ parser = argparse.ArgumentParser(description=__doc__)
72
+ parser.add_argument("--run-dir", required=True)
73
+ parser.add_argument(
74
+ "--confirm-appropriate-mirror",
75
+ action="store_true",
76
+ help="Required after visually confirming the rightward strip can be mirrored without identity/prop issues.",
77
+ )
78
+ parser.add_argument(
79
+ "--decision-note",
80
+ required=True,
81
+ help="Short note explaining why mirroring is acceptable for this pet.",
82
+ )
83
+ parser.add_argument("--force", action="store_true")
84
+ args = parser.parse_args()
85
+
86
+ if not args.confirm_appropriate_mirror:
87
+ raise SystemExit("refusing to mirror without --confirm-appropriate-mirror")
88
+ if not args.decision_note.strip():
89
+ raise SystemExit("--decision-note must explain why mirroring is appropriate")
90
+
91
+ run_dir = Path(args.run_dir).expanduser().resolve()
92
+ manifest_path = run_dir / "imagegen-jobs.json"
93
+ manifest = load_manifest(run_dir)
94
+ right_job = find_job(manifest, "running-right")
95
+ left_job = find_job(manifest, "running-left")
96
+
97
+ if right_job.get("status") != "complete":
98
+ raise SystemExit("running-right must be complete before deriving running-left")
99
+ mirror_policy = left_job.get("mirror_policy")
100
+ if not isinstance(mirror_policy, dict) or mirror_policy.get("may_derive_from") != "running-right":
101
+ raise SystemExit("running-left is not configured for conditional mirroring")
102
+
103
+ source = run_dir / "decoded" / "running-right.png"
104
+ output = run_dir / "decoded" / "running-left.png"
105
+ if not source.is_file():
106
+ raise SystemExit(f"running-right decoded strip not found: {source}")
107
+ if output.exists() and not args.force:
108
+ raise SystemExit(f"{output} already exists; pass --force to replace it")
109
+
110
+ output.parent.mkdir(parents=True, exist_ok=True)
111
+ with Image.open(source) as image:
112
+ mirrored = mirror_strip_preserving_frame_order(image)
113
+ mirrored.save(output)
114
+
115
+ left_job["status"] = "complete"
116
+ left_job["source_path"] = manifest_relative(source, run_dir)
117
+ left_job["derived_from"] = "running-right"
118
+ left_job["completed_at"] = datetime.now(timezone.utc).isoformat()
119
+ left_job["metadata"] = image_metadata(output)
120
+ left_job["mirror_decision"] = {
121
+ "approved": True,
122
+ "approved_at": left_job["completed_at"],
123
+ "note": args.decision_note.strip(),
124
+ "transform": "framewise-horizontal-mirror-preserving-order",
125
+ }
126
+ for key in [
127
+ "last_error",
128
+ "repair_reason",
129
+ "queued_at",
130
+ ]:
131
+ left_job.pop(key, None)
132
+
133
+ manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
134
+ print(
135
+ json.dumps(
136
+ {
137
+ "ok": True,
138
+ "job_id": "running-left",
139
+ "derived_from": "running-right",
140
+ "output": str(output),
141
+ "decision_note": args.decision_note.strip(),
142
+ "transform": "framewise-horizontal-mirror-preserving-order",
143
+ },
144
+ indent=2,
145
+ )
146
+ )
147
+
148
+
149
+ if __name__ == "__main__":
150
+ main()