qlogicagent 2.16.8 → 2.16.10

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 (35) hide show
  1. package/dist/agent.js +20 -20
  2. package/dist/cli.js +429 -396
  3. package/dist/default-project-knowledge/INSTRUCTIONS.md +7 -7
  4. package/dist/default-project-knowledge/rules/project-workflow.md +6 -6
  5. package/dist/index.js +425 -392
  6. package/dist/runtime/infra/mcp-bridge-server.js +338 -0
  7. package/dist/skills/mcp/astraclaw-native-mcp-server.js +9 -0
  8. package/dist/types/agent/tool-loop/artifact-final-contract.d.ts +3 -0
  9. package/dist/types/cli/handlers/turn-handler.d.ts +2 -0
  10. package/dist/types/cli/pet-runtime.d.ts +5 -0
  11. package/dist/types/cli/skills-query-service.d.ts +6 -1
  12. package/dist/types/orchestration/delegation-coordinator.d.ts +1 -0
  13. package/dist/types/runtime/infra/agent-process.d.ts +6 -0
  14. package/dist/types/runtime/infra/astraclaw-capabilities.d.ts +30 -0
  15. package/dist/types/runtime/infra/electron-node.d.ts +36 -0
  16. package/dist/types/runtime/infra/native-mcp-config-sync.d.ts +16 -0
  17. package/dist/types/runtime/infra/skill-resolver.d.ts +4 -0
  18. package/dist/types/skills/mcp/astraclaw-native-mcp-server.d.ts +1 -0
  19. package/dist/types/skills/plugins/plugin-marketplace.d.ts +16 -0
  20. package/dist/types/skills/skill-system/skill-validation.d.ts +10 -0
  21. package/dist/types/skills/tools/skill-tool.d.ts +1 -1
  22. package/dist/vendor/hatch-pet/LICENSE.txt +201 -201
  23. package/dist/vendor/hatch-pet/NOTICE.md +25 -25
  24. package/dist/vendor/hatch-pet/references/animation-rows.md +29 -29
  25. package/dist/vendor/hatch-pet/references/codex-pet-contract.md +35 -35
  26. package/dist/vendor/hatch-pet/references/qa-rubric.md +66 -66
  27. package/dist/vendor/hatch-pet/scripts/compose_atlas.py +169 -169
  28. package/dist/vendor/hatch-pet/scripts/derive_running_left_from_running_right.py +150 -150
  29. package/dist/vendor/hatch-pet/scripts/extract_strip_frames.py +408 -408
  30. package/dist/vendor/hatch-pet/scripts/inspect_frames.py +256 -256
  31. package/dist/vendor/hatch-pet/scripts/make_contact_sheet.py +96 -96
  32. package/dist/vendor/hatch-pet/scripts/prepare_pet_run.py +834 -834
  33. package/dist/vendor/hatch-pet/scripts/render_animation_previews.py +78 -78
  34. package/dist/vendor/hatch-pet/scripts/validate_atlas.py +157 -157
  35. package/package.json +1 -1
@@ -1,256 +1,256 @@
1
- #!/usr/bin/env python3
2
- """Inspect extracted Codex pet frames before atlas composition."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- import math
9
- from pathlib import Path
10
- from statistics import median
11
-
12
- from PIL import Image
13
-
14
- CELL_WIDTH = 192
15
- CELL_HEIGHT = 208
16
- ROW_FRAME_COUNTS = {
17
- "idle": 6,
18
- "running-right": 8,
19
- "running-left": 8,
20
- "waving": 4,
21
- "jumping": 5,
22
- "failed": 8,
23
- "waiting": 6,
24
- "running": 6,
25
- "review": 6,
26
- }
27
- IMAGE_SUFFIXES = {".png", ".webp", ".jpg", ".jpeg"}
28
-
29
-
30
- def alpha_nonzero_count(image: Image.Image) -> int:
31
- alpha = image if image.mode == "L" else image.getchannel("A")
32
- return sum(alpha.histogram()[1:])
33
-
34
-
35
- def edge_alpha_count(image: Image.Image, margin: int) -> int:
36
- alpha = image.getchannel("A")
37
- width, height = alpha.size
38
- total = 0
39
- for box in (
40
- (0, 0, width, margin),
41
- (0, height - margin, width, height),
42
- (0, 0, margin, height),
43
- (width - margin, 0, width, height),
44
- ):
45
- total += alpha_nonzero_count(alpha.crop(box))
46
- return total
47
-
48
-
49
- def color_distance(left: tuple[int, int, int], right: tuple[int, int, int]) -> float:
50
- return math.sqrt(sum((left[index] - right[index]) ** 2 for index in range(3)))
51
-
52
-
53
- def chroma_adjacent_count(
54
- image: Image.Image,
55
- chroma_key: tuple[int, int, int] | None,
56
- threshold: float,
57
- ) -> int:
58
- if chroma_key is None:
59
- return 0
60
- rgba = image.convert("RGBA")
61
- data = rgba.tobytes()
62
- count = 0
63
- for index in range(0, len(data), 4):
64
- red, green, blue, alpha = data[index : index + 4]
65
- if alpha > 16 and color_distance((red, green, blue), chroma_key) <= threshold:
66
- count += 1
67
- return count
68
-
69
-
70
- def frame_files(state_dir: Path) -> list[Path]:
71
- if not state_dir.is_dir():
72
- return []
73
- return sorted(path for path in state_dir.iterdir() if path.suffix.lower() in IMAGE_SUFFIXES)
74
-
75
-
76
- def load_manifest(frames_root: Path) -> dict[str, dict[str, object]]:
77
- manifest_path = frames_root / "frames-manifest.json"
78
- if not manifest_path.is_file():
79
- return {}
80
- manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
81
- rows = manifest.get("rows", [])
82
- if not isinstance(rows, list):
83
- return {}
84
- return {
85
- row["state"]: row
86
- for row in rows
87
- if isinstance(row, dict) and isinstance(row.get("state"), str)
88
- }
89
-
90
-
91
- def load_chroma_key(frames_root: Path) -> tuple[int, int, int] | None:
92
- manifest_path = frames_root / "frames-manifest.json"
93
- if not manifest_path.is_file():
94
- return None
95
- manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
96
- chroma_key = manifest.get("chroma_key")
97
- if not isinstance(chroma_key, dict):
98
- return None
99
- rgb = chroma_key.get("rgb")
100
- if (
101
- not isinstance(rgb, list)
102
- or len(rgb) != 3
103
- or not all(isinstance(value, int) for value in rgb)
104
- ):
105
- return None
106
- return (rgb[0], rgb[1], rgb[2])
107
-
108
-
109
- def inspect_state(
110
- frames_root: Path,
111
- state: str,
112
- expected_count: int,
113
- manifest_rows: dict[str, dict[str, object]],
114
- chroma_key: tuple[int, int, int] | None,
115
- args: argparse.Namespace,
116
- ) -> dict[str, object]:
117
- state_dir = frames_root / state
118
- files = frame_files(state_dir)
119
- row_errors: list[str] = []
120
- row_warnings: list[str] = []
121
- frames: list[dict[str, object]] = []
122
- areas: list[int] = []
123
- manifest_row = manifest_rows.get(state, {})
124
- method = manifest_row.get("method")
125
-
126
- if len(files) != expected_count:
127
- row_errors.append(f"expected {expected_count} frame files for {state}, found {len(files)}")
128
-
129
- if args.require_components and method and method != "components":
130
- if method == "stable-slots" and args.allow_stable_slots:
131
- row_warnings.append(
132
- f"{state} used extraction method stable-slots; confirm motion playback remains stable and unclipped"
133
- )
134
- else:
135
- row_errors.append(
136
- f"{state} used extraction method {method}; regenerate the row or inspect slot slicing"
137
- )
138
- elif method and method != "components":
139
- row_warnings.append(
140
- f"{state} used extraction method {method}; component extraction is preferred"
141
- )
142
-
143
- for index, frame_path in enumerate(files[:expected_count]):
144
- with Image.open(frame_path) as opened:
145
- frame = opened.convert("RGBA")
146
- nontransparent = alpha_nonzero_count(frame)
147
- bbox = frame.getbbox()
148
- edge_pixels = edge_alpha_count(frame, args.edge_margin)
149
- chroma_adjacent_pixels = chroma_adjacent_count(
150
- frame,
151
- chroma_key,
152
- args.chroma_adjacent_threshold,
153
- )
154
- info = {
155
- "index": index,
156
- "file": str(frame_path),
157
- "width": frame.width,
158
- "height": frame.height,
159
- "nontransparent_pixels": nontransparent,
160
- "bbox": list(bbox) if bbox else None,
161
- "edge_pixels": edge_pixels,
162
- "chroma_adjacent_pixels": chroma_adjacent_pixels,
163
- }
164
- frames.append(info)
165
- areas.append(nontransparent)
166
-
167
- if frame.size != (CELL_WIDTH, CELL_HEIGHT):
168
- row_errors.append(
169
- f"{state} frame {index:02d} is {frame.width}x{frame.height}; expected {CELL_WIDTH}x{CELL_HEIGHT}"
170
- )
171
- if nontransparent < args.min_used_pixels:
172
- row_errors.append(
173
- f"{state} frame {index:02d} is empty or too sparse ({nontransparent} pixels)"
174
- )
175
- if edge_pixels > args.edge_pixel_threshold:
176
- row_warnings.append(
177
- f"{state} frame {index:02d} has {edge_pixels} non-transparent pixels near the cell edge"
178
- )
179
- if chroma_adjacent_pixels > args.chroma_adjacent_pixel_threshold:
180
- row_errors.append(
181
- f"{state} frame {index:02d} has {chroma_adjacent_pixels} non-transparent pixels close to the chroma key"
182
- )
183
-
184
- if areas:
185
- row_median = median(areas)
186
- for index, area in enumerate(areas[:expected_count]):
187
- if row_median > 0 and area < row_median * args.small_outlier_ratio:
188
- row_warnings.append(
189
- f"{state} frame {index:02d} is much smaller than the row median ({area} vs {row_median:.0f})"
190
- )
191
- if row_median > 0 and area > row_median * args.large_outlier_ratio:
192
- row_warnings.append(
193
- f"{state} frame {index:02d} is much larger than the row median ({area} vs {row_median:.0f})"
194
- )
195
-
196
- return {
197
- "state": state,
198
- "expected_frames": expected_count,
199
- "actual_frames": len(files),
200
- "extraction_method": method,
201
- "ok": not row_errors,
202
- "errors": row_errors,
203
- "warnings": row_warnings,
204
- "frames": frames,
205
- }
206
-
207
-
208
- def main() -> None:
209
- parser = argparse.ArgumentParser(description=__doc__)
210
- parser.add_argument("--frames-root", required=True)
211
- parser.add_argument("--json-out", required=True)
212
- parser.add_argument("--min-used-pixels", type=int, default=400)
213
- parser.add_argument("--edge-margin", type=int, default=2)
214
- parser.add_argument("--edge-pixel-threshold", type=int, default=24)
215
- parser.add_argument("--chroma-adjacent-threshold", type=float, default=150.0)
216
- parser.add_argument("--chroma-adjacent-pixel-threshold", type=int, default=800)
217
- parser.add_argument("--small-outlier-ratio", type=float, default=0.35)
218
- parser.add_argument("--large-outlier-ratio", type=float, default=2.75)
219
- parser.add_argument(
220
- "--require-components",
221
- action="store_true",
222
- help="Fail rows that fell back to equal-slot extraction.",
223
- )
224
- parser.add_argument(
225
- "--allow-stable-slots",
226
- action="store_true",
227
- help="Permit explicitly chosen stable-slots extraction while still warning for visual review.",
228
- )
229
- args = parser.parse_args()
230
-
231
- frames_root = Path(args.frames_root).expanduser().resolve()
232
- manifest_rows = load_manifest(frames_root)
233
- chroma_key = load_chroma_key(frames_root)
234
- rows = [
235
- inspect_state(frames_root, state, count, manifest_rows, chroma_key, args)
236
- for state, count in ROW_FRAME_COUNTS.items()
237
- ]
238
- errors = [error for row in rows for error in row["errors"]]
239
- warnings = [warning for row in rows for warning in row["warnings"]]
240
- result = {
241
- "ok": not errors,
242
- "frames_root": str(frames_root),
243
- "errors": errors,
244
- "warnings": warnings,
245
- "rows": rows,
246
- }
247
-
248
- json_out = Path(args.json_out).expanduser().resolve()
249
- json_out.parent.mkdir(parents=True, exist_ok=True)
250
- json_out.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
251
- print(json.dumps({k: v for k, v in result.items() if k != "rows"}, indent=2))
252
- raise SystemExit(0 if result["ok"] else 1)
253
-
254
-
255
- if __name__ == "__main__":
256
- main()
1
+ #!/usr/bin/env python3
2
+ """Inspect extracted Codex pet frames before atlas composition."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import math
9
+ from pathlib import Path
10
+ from statistics import median
11
+
12
+ from PIL import Image
13
+
14
+ CELL_WIDTH = 192
15
+ CELL_HEIGHT = 208
16
+ ROW_FRAME_COUNTS = {
17
+ "idle": 6,
18
+ "running-right": 8,
19
+ "running-left": 8,
20
+ "waving": 4,
21
+ "jumping": 5,
22
+ "failed": 8,
23
+ "waiting": 6,
24
+ "running": 6,
25
+ "review": 6,
26
+ }
27
+ IMAGE_SUFFIXES = {".png", ".webp", ".jpg", ".jpeg"}
28
+
29
+
30
+ def alpha_nonzero_count(image: Image.Image) -> int:
31
+ alpha = image if image.mode == "L" else image.getchannel("A")
32
+ return sum(alpha.histogram()[1:])
33
+
34
+
35
+ def edge_alpha_count(image: Image.Image, margin: int) -> int:
36
+ alpha = image.getchannel("A")
37
+ width, height = alpha.size
38
+ total = 0
39
+ for box in (
40
+ (0, 0, width, margin),
41
+ (0, height - margin, width, height),
42
+ (0, 0, margin, height),
43
+ (width - margin, 0, width, height),
44
+ ):
45
+ total += alpha_nonzero_count(alpha.crop(box))
46
+ return total
47
+
48
+
49
+ def color_distance(left: tuple[int, int, int], right: tuple[int, int, int]) -> float:
50
+ return math.sqrt(sum((left[index] - right[index]) ** 2 for index in range(3)))
51
+
52
+
53
+ def chroma_adjacent_count(
54
+ image: Image.Image,
55
+ chroma_key: tuple[int, int, int] | None,
56
+ threshold: float,
57
+ ) -> int:
58
+ if chroma_key is None:
59
+ return 0
60
+ rgba = image.convert("RGBA")
61
+ data = rgba.tobytes()
62
+ count = 0
63
+ for index in range(0, len(data), 4):
64
+ red, green, blue, alpha = data[index : index + 4]
65
+ if alpha > 16 and color_distance((red, green, blue), chroma_key) <= threshold:
66
+ count += 1
67
+ return count
68
+
69
+
70
+ def frame_files(state_dir: Path) -> list[Path]:
71
+ if not state_dir.is_dir():
72
+ return []
73
+ return sorted(path for path in state_dir.iterdir() if path.suffix.lower() in IMAGE_SUFFIXES)
74
+
75
+
76
+ def load_manifest(frames_root: Path) -> dict[str, dict[str, object]]:
77
+ manifest_path = frames_root / "frames-manifest.json"
78
+ if not manifest_path.is_file():
79
+ return {}
80
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
81
+ rows = manifest.get("rows", [])
82
+ if not isinstance(rows, list):
83
+ return {}
84
+ return {
85
+ row["state"]: row
86
+ for row in rows
87
+ if isinstance(row, dict) and isinstance(row.get("state"), str)
88
+ }
89
+
90
+
91
+ def load_chroma_key(frames_root: Path) -> tuple[int, int, int] | None:
92
+ manifest_path = frames_root / "frames-manifest.json"
93
+ if not manifest_path.is_file():
94
+ return None
95
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
96
+ chroma_key = manifest.get("chroma_key")
97
+ if not isinstance(chroma_key, dict):
98
+ return None
99
+ rgb = chroma_key.get("rgb")
100
+ if (
101
+ not isinstance(rgb, list)
102
+ or len(rgb) != 3
103
+ or not all(isinstance(value, int) for value in rgb)
104
+ ):
105
+ return None
106
+ return (rgb[0], rgb[1], rgb[2])
107
+
108
+
109
+ def inspect_state(
110
+ frames_root: Path,
111
+ state: str,
112
+ expected_count: int,
113
+ manifest_rows: dict[str, dict[str, object]],
114
+ chroma_key: tuple[int, int, int] | None,
115
+ args: argparse.Namespace,
116
+ ) -> dict[str, object]:
117
+ state_dir = frames_root / state
118
+ files = frame_files(state_dir)
119
+ row_errors: list[str] = []
120
+ row_warnings: list[str] = []
121
+ frames: list[dict[str, object]] = []
122
+ areas: list[int] = []
123
+ manifest_row = manifest_rows.get(state, {})
124
+ method = manifest_row.get("method")
125
+
126
+ if len(files) != expected_count:
127
+ row_errors.append(f"expected {expected_count} frame files for {state}, found {len(files)}")
128
+
129
+ if args.require_components and method and method != "components":
130
+ if method == "stable-slots" and args.allow_stable_slots:
131
+ row_warnings.append(
132
+ f"{state} used extraction method stable-slots; confirm motion playback remains stable and unclipped"
133
+ )
134
+ else:
135
+ row_errors.append(
136
+ f"{state} used extraction method {method}; regenerate the row or inspect slot slicing"
137
+ )
138
+ elif method and method != "components":
139
+ row_warnings.append(
140
+ f"{state} used extraction method {method}; component extraction is preferred"
141
+ )
142
+
143
+ for index, frame_path in enumerate(files[:expected_count]):
144
+ with Image.open(frame_path) as opened:
145
+ frame = opened.convert("RGBA")
146
+ nontransparent = alpha_nonzero_count(frame)
147
+ bbox = frame.getbbox()
148
+ edge_pixels = edge_alpha_count(frame, args.edge_margin)
149
+ chroma_adjacent_pixels = chroma_adjacent_count(
150
+ frame,
151
+ chroma_key,
152
+ args.chroma_adjacent_threshold,
153
+ )
154
+ info = {
155
+ "index": index,
156
+ "file": str(frame_path),
157
+ "width": frame.width,
158
+ "height": frame.height,
159
+ "nontransparent_pixels": nontransparent,
160
+ "bbox": list(bbox) if bbox else None,
161
+ "edge_pixels": edge_pixels,
162
+ "chroma_adjacent_pixels": chroma_adjacent_pixels,
163
+ }
164
+ frames.append(info)
165
+ areas.append(nontransparent)
166
+
167
+ if frame.size != (CELL_WIDTH, CELL_HEIGHT):
168
+ row_errors.append(
169
+ f"{state} frame {index:02d} is {frame.width}x{frame.height}; expected {CELL_WIDTH}x{CELL_HEIGHT}"
170
+ )
171
+ if nontransparent < args.min_used_pixels:
172
+ row_errors.append(
173
+ f"{state} frame {index:02d} is empty or too sparse ({nontransparent} pixels)"
174
+ )
175
+ if edge_pixels > args.edge_pixel_threshold:
176
+ row_warnings.append(
177
+ f"{state} frame {index:02d} has {edge_pixels} non-transparent pixels near the cell edge"
178
+ )
179
+ if chroma_adjacent_pixels > args.chroma_adjacent_pixel_threshold:
180
+ row_errors.append(
181
+ f"{state} frame {index:02d} has {chroma_adjacent_pixels} non-transparent pixels close to the chroma key"
182
+ )
183
+
184
+ if areas:
185
+ row_median = median(areas)
186
+ for index, area in enumerate(areas[:expected_count]):
187
+ if row_median > 0 and area < row_median * args.small_outlier_ratio:
188
+ row_warnings.append(
189
+ f"{state} frame {index:02d} is much smaller than the row median ({area} vs {row_median:.0f})"
190
+ )
191
+ if row_median > 0 and area > row_median * args.large_outlier_ratio:
192
+ row_warnings.append(
193
+ f"{state} frame {index:02d} is much larger than the row median ({area} vs {row_median:.0f})"
194
+ )
195
+
196
+ return {
197
+ "state": state,
198
+ "expected_frames": expected_count,
199
+ "actual_frames": len(files),
200
+ "extraction_method": method,
201
+ "ok": not row_errors,
202
+ "errors": row_errors,
203
+ "warnings": row_warnings,
204
+ "frames": frames,
205
+ }
206
+
207
+
208
+ def main() -> None:
209
+ parser = argparse.ArgumentParser(description=__doc__)
210
+ parser.add_argument("--frames-root", required=True)
211
+ parser.add_argument("--json-out", required=True)
212
+ parser.add_argument("--min-used-pixels", type=int, default=400)
213
+ parser.add_argument("--edge-margin", type=int, default=2)
214
+ parser.add_argument("--edge-pixel-threshold", type=int, default=24)
215
+ parser.add_argument("--chroma-adjacent-threshold", type=float, default=150.0)
216
+ parser.add_argument("--chroma-adjacent-pixel-threshold", type=int, default=800)
217
+ parser.add_argument("--small-outlier-ratio", type=float, default=0.35)
218
+ parser.add_argument("--large-outlier-ratio", type=float, default=2.75)
219
+ parser.add_argument(
220
+ "--require-components",
221
+ action="store_true",
222
+ help="Fail rows that fell back to equal-slot extraction.",
223
+ )
224
+ parser.add_argument(
225
+ "--allow-stable-slots",
226
+ action="store_true",
227
+ help="Permit explicitly chosen stable-slots extraction while still warning for visual review.",
228
+ )
229
+ args = parser.parse_args()
230
+
231
+ frames_root = Path(args.frames_root).expanduser().resolve()
232
+ manifest_rows = load_manifest(frames_root)
233
+ chroma_key = load_chroma_key(frames_root)
234
+ rows = [
235
+ inspect_state(frames_root, state, count, manifest_rows, chroma_key, args)
236
+ for state, count in ROW_FRAME_COUNTS.items()
237
+ ]
238
+ errors = [error for row in rows for error in row["errors"]]
239
+ warnings = [warning for row in rows for warning in row["warnings"]]
240
+ result = {
241
+ "ok": not errors,
242
+ "frames_root": str(frames_root),
243
+ "errors": errors,
244
+ "warnings": warnings,
245
+ "rows": rows,
246
+ }
247
+
248
+ json_out = Path(args.json_out).expanduser().resolve()
249
+ json_out.parent.mkdir(parents=True, exist_ok=True)
250
+ json_out.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
251
+ print(json.dumps({k: v for k, v in result.items() if k != "rows"}, indent=2))
252
+ raise SystemExit(0 if result["ok"] else 1)
253
+
254
+
255
+ if __name__ == "__main__":
256
+ main()