qlogicagent 2.18.11 → 2.19.1

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 (37) hide show
  1. package/README.md +382 -382
  2. package/dist/agent-contract.js +1 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/default-project-knowledge/INSTRUCTIONS.md +7 -7
  5. package/dist/default-project-knowledge/rules/project-workflow.md +6 -6
  6. package/dist/host-contract.js +1 -1
  7. package/dist/index.js +340 -340
  8. package/dist/protocol.js +1 -1
  9. package/dist/skills/builtin/desktop-screenshot/SKILL.md +62 -62
  10. package/dist/skills/builtin/office-doc-reading/SKILL.md +19 -19
  11. package/dist/types/cli/acp-extended-handlers.d.ts +6 -0
  12. package/dist/types/cli/handlers/agents-handler.d.ts +5 -0
  13. package/dist/types/host-contract/index.d.ts +2 -1
  14. package/dist/types/orchestration/agent-instance.d.ts +7 -3
  15. package/dist/types/orchestration/agent-roster.d.ts +6 -2
  16. package/dist/types/orchestration/delegation-coordinator.d.ts +2 -2
  17. package/dist/types/protocol/agent-contract.d.ts +44 -0
  18. package/dist/types/protocol/notifications.d.ts +1 -1
  19. package/dist/types/protocol/wire/acp-protocol.d.ts +3 -0
  20. package/dist/types/protocol/wire/agent-events.d.ts +1 -32
  21. package/dist/types/protocol/wire/index.d.ts +1 -1
  22. package/dist/types/protocol/wire/notification-payloads.d.ts +63 -0
  23. package/dist/types/runtime/infra/acp-detector.d.ts +2 -0
  24. package/dist/vendor/hatch-pet/LICENSE.txt +201 -201
  25. package/dist/vendor/hatch-pet/NOTICE.md +25 -25
  26. package/dist/vendor/hatch-pet/references/animation-rows.md +29 -29
  27. package/dist/vendor/hatch-pet/references/codex-pet-contract.md +35 -35
  28. package/dist/vendor/hatch-pet/references/qa-rubric.md +66 -66
  29. package/dist/vendor/hatch-pet/scripts/compose_atlas.py +169 -169
  30. package/dist/vendor/hatch-pet/scripts/derive_running_left_from_running_right.py +150 -150
  31. package/dist/vendor/hatch-pet/scripts/extract_strip_frames.py +408 -408
  32. package/dist/vendor/hatch-pet/scripts/inspect_frames.py +256 -256
  33. package/dist/vendor/hatch-pet/scripts/make_contact_sheet.py +96 -96
  34. package/dist/vendor/hatch-pet/scripts/prepare_pet_run.py +834 -834
  35. package/dist/vendor/hatch-pet/scripts/render_animation_previews.py +78 -78
  36. package/dist/vendor/hatch-pet/scripts/validate_atlas.py +157 -157
  37. package/package.json +9 -1
@@ -1,78 +1,78 @@
1
- #!/usr/bin/env python3
2
- """Render lightweight animated QA previews from extracted Codex pet frames."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- from pathlib import Path
9
-
10
- from PIL import Image
11
-
12
- ROW_DURATIONS = {
13
- "idle": [280, 110, 110, 140, 140, 320],
14
- "running-right": [120, 120, 120, 120, 120, 120, 120, 220],
15
- "running-left": [120, 120, 120, 120, 120, 120, 120, 220],
16
- "waving": [140, 140, 140, 280],
17
- "jumping": [140, 140, 140, 140, 280],
18
- "failed": [140, 140, 140, 140, 140, 140, 140, 240],
19
- "waiting": [150, 150, 150, 150, 150, 260],
20
- "running": [120, 120, 120, 120, 120, 220],
21
- "review": [150, 150, 150, 150, 150, 280],
22
- }
23
- IMAGE_SUFFIXES = {".png", ".webp", ".jpg", ".jpeg"}
24
-
25
-
26
- def frame_files(state_dir: Path) -> list[Path]:
27
- if not state_dir.is_dir():
28
- return []
29
- return sorted(path for path in state_dir.iterdir() if path.suffix.lower() in IMAGE_SUFFIXES)
30
-
31
-
32
- def load_frames(frames_root: Path, state: str, expected_count: int) -> list[Image.Image]:
33
- files = frame_files(frames_root / state)
34
- if len(files) != expected_count:
35
- raise SystemExit(
36
- f"{state} preview needs {expected_count} frames, found {len(files)} under {frames_root / state}"
37
- )
38
- frames = []
39
- for path in files:
40
- with Image.open(path) as opened:
41
- frames.append(opened.convert("RGBA"))
42
- return frames
43
-
44
-
45
- def save_preview(frames: list[Image.Image], durations: list[int], output: Path) -> None:
46
- output.parent.mkdir(parents=True, exist_ok=True)
47
- frames[0].save(
48
- output,
49
- save_all=True,
50
- append_images=frames[1:],
51
- duration=durations,
52
- loop=0,
53
- disposal=2,
54
- optimize=False,
55
- )
56
-
57
-
58
- def main() -> None:
59
- parser = argparse.ArgumentParser(description=__doc__)
60
- parser.add_argument("--frames-root", required=True)
61
- parser.add_argument("--output-dir", required=True)
62
- args = parser.parse_args()
63
-
64
- frames_root = Path(args.frames_root).expanduser().resolve()
65
- output_dir = Path(args.output_dir).expanduser().resolve()
66
- previews = []
67
- for state, durations in ROW_DURATIONS.items():
68
- frames = load_frames(frames_root, state, len(durations))
69
- output = output_dir / f"{state}.gif"
70
- save_preview(frames, durations, output)
71
- previews.append({"state": state, "path": str(output), "frames": len(frames)})
72
-
73
- result = {"ok": True, "output_dir": str(output_dir), "previews": previews}
74
- print(json.dumps(result, indent=2))
75
-
76
-
77
- if __name__ == "__main__":
78
- main()
1
+ #!/usr/bin/env python3
2
+ """Render lightweight animated QA previews from extracted Codex pet frames."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from pathlib import Path
9
+
10
+ from PIL import Image
11
+
12
+ ROW_DURATIONS = {
13
+ "idle": [280, 110, 110, 140, 140, 320],
14
+ "running-right": [120, 120, 120, 120, 120, 120, 120, 220],
15
+ "running-left": [120, 120, 120, 120, 120, 120, 120, 220],
16
+ "waving": [140, 140, 140, 280],
17
+ "jumping": [140, 140, 140, 140, 280],
18
+ "failed": [140, 140, 140, 140, 140, 140, 140, 240],
19
+ "waiting": [150, 150, 150, 150, 150, 260],
20
+ "running": [120, 120, 120, 120, 120, 220],
21
+ "review": [150, 150, 150, 150, 150, 280],
22
+ }
23
+ IMAGE_SUFFIXES = {".png", ".webp", ".jpg", ".jpeg"}
24
+
25
+
26
+ def frame_files(state_dir: Path) -> list[Path]:
27
+ if not state_dir.is_dir():
28
+ return []
29
+ return sorted(path for path in state_dir.iterdir() if path.suffix.lower() in IMAGE_SUFFIXES)
30
+
31
+
32
+ def load_frames(frames_root: Path, state: str, expected_count: int) -> list[Image.Image]:
33
+ files = frame_files(frames_root / state)
34
+ if len(files) != expected_count:
35
+ raise SystemExit(
36
+ f"{state} preview needs {expected_count} frames, found {len(files)} under {frames_root / state}"
37
+ )
38
+ frames = []
39
+ for path in files:
40
+ with Image.open(path) as opened:
41
+ frames.append(opened.convert("RGBA"))
42
+ return frames
43
+
44
+
45
+ def save_preview(frames: list[Image.Image], durations: list[int], output: Path) -> None:
46
+ output.parent.mkdir(parents=True, exist_ok=True)
47
+ frames[0].save(
48
+ output,
49
+ save_all=True,
50
+ append_images=frames[1:],
51
+ duration=durations,
52
+ loop=0,
53
+ disposal=2,
54
+ optimize=False,
55
+ )
56
+
57
+
58
+ def main() -> None:
59
+ parser = argparse.ArgumentParser(description=__doc__)
60
+ parser.add_argument("--frames-root", required=True)
61
+ parser.add_argument("--output-dir", required=True)
62
+ args = parser.parse_args()
63
+
64
+ frames_root = Path(args.frames_root).expanduser().resolve()
65
+ output_dir = Path(args.output_dir).expanduser().resolve()
66
+ previews = []
67
+ for state, durations in ROW_DURATIONS.items():
68
+ frames = load_frames(frames_root, state, len(durations))
69
+ output = output_dir / f"{state}.gif"
70
+ save_preview(frames, durations, output)
71
+ previews.append({"state": state, "path": str(output), "frames": len(frames)})
72
+
73
+ result = {"ok": True, "output_dir": str(output_dir), "previews": previews}
74
+ print(json.dumps(result, indent=2))
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -1,157 +1,157 @@
1
- #!/usr/bin/env python3
2
- """Validate a Codex pet spritesheet atlas."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- from collections import defaultdict
9
- from pathlib import Path
10
-
11
- from PIL import Image
12
-
13
- COLUMNS = 8
14
- ROWS = 9
15
- CELL_WIDTH = 192
16
- CELL_HEIGHT = 208
17
- ATLAS_WIDTH = COLUMNS * CELL_WIDTH
18
- ATLAS_HEIGHT = ROWS * CELL_HEIGHT
19
- ROW_BY_INDEX = {
20
- 0: ("idle", 6),
21
- 1: ("running-right", 8),
22
- 2: ("running-left", 8),
23
- 3: ("waving", 4),
24
- 4: ("jumping", 5),
25
- 5: ("failed", 8),
26
- 6: ("waiting", 6),
27
- 7: ("running", 6),
28
- 8: ("review", 6),
29
- }
30
-
31
-
32
- def alpha_nonzero_count(image: Image.Image) -> int:
33
- alpha = image.getchannel("A")
34
- return sum(alpha.histogram()[1:])
35
-
36
-
37
- def transparent_rgb_residue_count(image: Image.Image) -> int:
38
- rgba = image.convert("RGBA")
39
- data = rgba.tobytes()
40
- count = 0
41
- for index in range(0, len(data), 4):
42
- red, green, blue, alpha = data[index : index + 4]
43
- if alpha == 0 and (red or green or blue):
44
- count += 1
45
- return count
46
-
47
-
48
- def main() -> None:
49
- parser = argparse.ArgumentParser(description=__doc__)
50
- parser.add_argument("atlas")
51
- parser.add_argument("--json-out")
52
- parser.add_argument("--min-used-pixels", type=int, default=50)
53
- parser.add_argument("--near-opaque-threshold", type=float, default=0.95)
54
- parser.add_argument("--allow-opaque", action="store_true")
55
- parser.add_argument("--allow-near-opaque-used-cells", action="store_true")
56
- args = parser.parse_args()
57
-
58
- atlas_path = Path(args.atlas).expanduser().resolve()
59
- errors: list[str] = []
60
- warnings: list[str] = []
61
- near_opaque_used_cells: dict[str, list[int]] = defaultdict(list)
62
- cells: list[dict[str, object]] = []
63
-
64
- try:
65
- with Image.open(atlas_path) as opened:
66
- source_mode = opened.mode
67
- source_format = opened.format
68
- image = opened.convert("RGBA")
69
- except Exception as exc: # noqa: BLE001
70
- result = {"ok": False, "errors": [f"could not open atlas: {exc}"], "warnings": []}
71
- print(json.dumps(result, indent=2))
72
- raise SystemExit(1)
73
-
74
- if image.size != (ATLAS_WIDTH, ATLAS_HEIGHT):
75
- errors.append(f"expected {ATLAS_WIDTH}x{ATLAS_HEIGHT}, got {image.width}x{image.height}")
76
-
77
- if source_format not in {"PNG", "WEBP"}:
78
- errors.append(f"expected PNG or WebP, got {source_format}")
79
-
80
- if "A" not in source_mode and not args.allow_opaque:
81
- errors.append("atlas does not have an alpha channel")
82
-
83
- for row_index in range(ROWS):
84
- state, frame_count = ROW_BY_INDEX[row_index]
85
- for column_index in range(COLUMNS):
86
- left = column_index * CELL_WIDTH
87
- top = row_index * CELL_HEIGHT
88
- cell = image.crop((left, top, left + CELL_WIDTH, top + CELL_HEIGHT))
89
- nontransparent = alpha_nonzero_count(cell)
90
- used = column_index < frame_count
91
- cell_info = {
92
- "state": state,
93
- "row": row_index,
94
- "column": column_index,
95
- "used": used,
96
- "nontransparent_pixels": nontransparent,
97
- }
98
- cells.append(cell_info)
99
- if used and nontransparent < args.min_used_pixels:
100
- errors.append(
101
- f"{state} row {row_index} column {column_index} is empty or too sparse ({nontransparent} pixels)"
102
- )
103
- if used and nontransparent > CELL_WIDTH * CELL_HEIGHT * args.near_opaque_threshold:
104
- near_opaque_used_cells[f"{state} row {row_index}"].append(column_index)
105
- if not used and nontransparent != 0:
106
- errors.append(
107
- f"{state} row {row_index} unused column {column_index} is not transparent ({nontransparent} pixels)"
108
- )
109
-
110
- for row_label, columns in near_opaque_used_cells.items():
111
- message = (
112
- f"{row_label} has {len(columns)} nearly opaque used cells; "
113
- "this usually means the sprite has a non-transparent background"
114
- )
115
- if args.allow_near_opaque_used_cells:
116
- warnings.append(message)
117
- else:
118
- errors.append(message)
119
-
120
- alpha_count = alpha_nonzero_count(image)
121
- if alpha_count == ATLAS_WIDTH * ATLAS_HEIGHT:
122
- message = "atlas is fully opaque; custom pets require a transparent sprite background"
123
- if args.allow_opaque:
124
- warnings.append(message)
125
- else:
126
- errors.append(message)
127
-
128
- transparent_rgb_residue = transparent_rgb_residue_count(image)
129
- if transparent_rgb_residue:
130
- errors.append(
131
- f"atlas has {transparent_rgb_residue} fully transparent pixels with non-zero RGB residue"
132
- )
133
-
134
- result = {
135
- "ok": not errors,
136
- "file": str(atlas_path),
137
- "format": source_format,
138
- "mode": source_mode,
139
- "width": image.width,
140
- "height": image.height,
141
- "transparent_rgb_residue_pixels": transparent_rgb_residue,
142
- "errors": errors,
143
- "warnings": warnings,
144
- "cells": cells,
145
- }
146
-
147
- if args.json_out:
148
- Path(args.json_out).expanduser().resolve().write_text(
149
- json.dumps(result, indent=2) + "\n", encoding="utf-8"
150
- )
151
-
152
- print(json.dumps({k: v for k, v in result.items() if k != "cells"}, indent=2))
153
- raise SystemExit(0 if result["ok"] else 1)
154
-
155
-
156
- if __name__ == "__main__":
157
- main()
1
+ #!/usr/bin/env python3
2
+ """Validate a Codex pet spritesheet atlas."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from collections import defaultdict
9
+ from pathlib import Path
10
+
11
+ from PIL import Image
12
+
13
+ COLUMNS = 8
14
+ ROWS = 9
15
+ CELL_WIDTH = 192
16
+ CELL_HEIGHT = 208
17
+ ATLAS_WIDTH = COLUMNS * CELL_WIDTH
18
+ ATLAS_HEIGHT = ROWS * CELL_HEIGHT
19
+ ROW_BY_INDEX = {
20
+ 0: ("idle", 6),
21
+ 1: ("running-right", 8),
22
+ 2: ("running-left", 8),
23
+ 3: ("waving", 4),
24
+ 4: ("jumping", 5),
25
+ 5: ("failed", 8),
26
+ 6: ("waiting", 6),
27
+ 7: ("running", 6),
28
+ 8: ("review", 6),
29
+ }
30
+
31
+
32
+ def alpha_nonzero_count(image: Image.Image) -> int:
33
+ alpha = image.getchannel("A")
34
+ return sum(alpha.histogram()[1:])
35
+
36
+
37
+ def transparent_rgb_residue_count(image: Image.Image) -> int:
38
+ rgba = image.convert("RGBA")
39
+ data = rgba.tobytes()
40
+ count = 0
41
+ for index in range(0, len(data), 4):
42
+ red, green, blue, alpha = data[index : index + 4]
43
+ if alpha == 0 and (red or green or blue):
44
+ count += 1
45
+ return count
46
+
47
+
48
+ def main() -> None:
49
+ parser = argparse.ArgumentParser(description=__doc__)
50
+ parser.add_argument("atlas")
51
+ parser.add_argument("--json-out")
52
+ parser.add_argument("--min-used-pixels", type=int, default=50)
53
+ parser.add_argument("--near-opaque-threshold", type=float, default=0.95)
54
+ parser.add_argument("--allow-opaque", action="store_true")
55
+ parser.add_argument("--allow-near-opaque-used-cells", action="store_true")
56
+ args = parser.parse_args()
57
+
58
+ atlas_path = Path(args.atlas).expanduser().resolve()
59
+ errors: list[str] = []
60
+ warnings: list[str] = []
61
+ near_opaque_used_cells: dict[str, list[int]] = defaultdict(list)
62
+ cells: list[dict[str, object]] = []
63
+
64
+ try:
65
+ with Image.open(atlas_path) as opened:
66
+ source_mode = opened.mode
67
+ source_format = opened.format
68
+ image = opened.convert("RGBA")
69
+ except Exception as exc: # noqa: BLE001
70
+ result = {"ok": False, "errors": [f"could not open atlas: {exc}"], "warnings": []}
71
+ print(json.dumps(result, indent=2))
72
+ raise SystemExit(1)
73
+
74
+ if image.size != (ATLAS_WIDTH, ATLAS_HEIGHT):
75
+ errors.append(f"expected {ATLAS_WIDTH}x{ATLAS_HEIGHT}, got {image.width}x{image.height}")
76
+
77
+ if source_format not in {"PNG", "WEBP"}:
78
+ errors.append(f"expected PNG or WebP, got {source_format}")
79
+
80
+ if "A" not in source_mode and not args.allow_opaque:
81
+ errors.append("atlas does not have an alpha channel")
82
+
83
+ for row_index in range(ROWS):
84
+ state, frame_count = ROW_BY_INDEX[row_index]
85
+ for column_index in range(COLUMNS):
86
+ left = column_index * CELL_WIDTH
87
+ top = row_index * CELL_HEIGHT
88
+ cell = image.crop((left, top, left + CELL_WIDTH, top + CELL_HEIGHT))
89
+ nontransparent = alpha_nonzero_count(cell)
90
+ used = column_index < frame_count
91
+ cell_info = {
92
+ "state": state,
93
+ "row": row_index,
94
+ "column": column_index,
95
+ "used": used,
96
+ "nontransparent_pixels": nontransparent,
97
+ }
98
+ cells.append(cell_info)
99
+ if used and nontransparent < args.min_used_pixels:
100
+ errors.append(
101
+ f"{state} row {row_index} column {column_index} is empty or too sparse ({nontransparent} pixels)"
102
+ )
103
+ if used and nontransparent > CELL_WIDTH * CELL_HEIGHT * args.near_opaque_threshold:
104
+ near_opaque_used_cells[f"{state} row {row_index}"].append(column_index)
105
+ if not used and nontransparent != 0:
106
+ errors.append(
107
+ f"{state} row {row_index} unused column {column_index} is not transparent ({nontransparent} pixels)"
108
+ )
109
+
110
+ for row_label, columns in near_opaque_used_cells.items():
111
+ message = (
112
+ f"{row_label} has {len(columns)} nearly opaque used cells; "
113
+ "this usually means the sprite has a non-transparent background"
114
+ )
115
+ if args.allow_near_opaque_used_cells:
116
+ warnings.append(message)
117
+ else:
118
+ errors.append(message)
119
+
120
+ alpha_count = alpha_nonzero_count(image)
121
+ if alpha_count == ATLAS_WIDTH * ATLAS_HEIGHT:
122
+ message = "atlas is fully opaque; custom pets require a transparent sprite background"
123
+ if args.allow_opaque:
124
+ warnings.append(message)
125
+ else:
126
+ errors.append(message)
127
+
128
+ transparent_rgb_residue = transparent_rgb_residue_count(image)
129
+ if transparent_rgb_residue:
130
+ errors.append(
131
+ f"atlas has {transparent_rgb_residue} fully transparent pixels with non-zero RGB residue"
132
+ )
133
+
134
+ result = {
135
+ "ok": not errors,
136
+ "file": str(atlas_path),
137
+ "format": source_format,
138
+ "mode": source_mode,
139
+ "width": image.width,
140
+ "height": image.height,
141
+ "transparent_rgb_residue_pixels": transparent_rgb_residue,
142
+ "errors": errors,
143
+ "warnings": warnings,
144
+ "cells": cells,
145
+ }
146
+
147
+ if args.json_out:
148
+ Path(args.json_out).expanduser().resolve().write_text(
149
+ json.dumps(result, indent=2) + "\n", encoding="utf-8"
150
+ )
151
+
152
+ print(json.dumps({k: v for k, v in result.items() if k != "cells"}, indent=2))
153
+ raise SystemExit(0 if result["ok"] else 1)
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qlogicagent",
3
- "version": "2.18.11",
3
+ "version": "2.19.1",
4
4
  "description": "XiaozhiClaw Agent CLI — subprocess architecture (JSON-RPC over stdio)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/types/protocol/wire/index.d.ts",
29
29
  "default": "./dist/protocol.js"
30
30
  },
31
+ "./agent-contract": {
32
+ "types": "./dist/types/protocol/agent-contract.d.ts",
33
+ "default": "./dist/agent-contract.js"
34
+ },
31
35
  "./permissions": {
32
36
  "types": "./dist/types/permissions.d.ts",
33
37
  "default": "./dist/permissions.js"
@@ -69,6 +73,9 @@
69
73
  "protocol": [
70
74
  "./dist/types/protocol/wire/index.d.ts"
71
75
  ],
76
+ "agent-contract": [
77
+ "./dist/types/protocol/agent-contract.d.ts"
78
+ ],
72
79
  "agent": [
73
80
  "./dist/types/agent/agent.d.ts"
74
81
  ],
@@ -123,6 +130,7 @@
123
130
  "check:windows-hide": "node scripts/check-windows-hide.mjs src",
124
131
  "check:test-suite-governance": "node scripts/check-test-suite-governance.mjs",
125
132
  "check:architecture-boundaries": "node scripts/check-architecture-boundaries.mjs",
133
+ "check:agent-contract": "node scripts/check-agent-contract-hash.mjs",
126
134
  "check:route-a-teardown": "node scripts/check-route-a-teardown.mjs",
127
135
  "check:transcript-tool-persistence": "node scripts/check-transcript-tool-persistence.mjs",
128
136
  "check:provider-core-boundary": "node scripts/check-provider-core-boundary.mjs",