@pushpalsdev/cli 1.0.18 → 1.0.20

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 (108) hide show
  1. package/dist/pushpals-cli.js +291 -44
  2. package/package.json +1 -1
  3. package/runtime/configs/backend.toml +1 -1
  4. package/runtime/configs/default.toml +1 -1
  5. package/runtime/sandbox/apps/workerpals/.python-version +1 -0
  6. package/runtime/sandbox/apps/workerpals/Dockerfile.sandbox +71 -0
  7. package/runtime/sandbox/apps/workerpals/package.json +25 -0
  8. package/runtime/sandbox/apps/workerpals/pyproject.toml +8 -0
  9. package/runtime/sandbox/apps/workerpals/src/backends/backend_config.ts +119 -0
  10. package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +2029 -0
  11. package/runtime/sandbox/apps/workerpals/src/backends/miniswe_backend.ts +48 -0
  12. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +1259 -0
  13. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +110 -0
  14. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +67 -0
  15. package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +563 -0
  16. package/runtime/sandbox/apps/workerpals/src/backends/openhands_backend.ts +161 -0
  17. package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +536 -0
  18. package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +746 -0
  19. package/runtime/sandbox/apps/workerpals/src/backends/shared/test_settings_resolver.py +60 -0
  20. package/runtime/sandbox/apps/workerpals/src/backends/task_execute_registry.ts +21 -0
  21. package/runtime/sandbox/apps/workerpals/src/backends/types.ts +52 -0
  22. package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +149 -0
  23. package/runtime/sandbox/apps/workerpals/src/common/executor_backend.ts +15 -0
  24. package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +210 -0
  25. package/runtime/sandbox/apps/workerpals/src/common/logger.ts +65 -0
  26. package/runtime/sandbox/apps/workerpals/src/common/types.ts +9 -0
  27. package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +66 -0
  28. package/runtime/sandbox/apps/workerpals/src/context_manager.ts +45 -0
  29. package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +1842 -0
  30. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +3063 -0
  31. package/runtime/sandbox/apps/workerpals/src/job_runner.ts +194 -0
  32. package/runtime/sandbox/apps/workerpals/src/shell_manager.ts +210 -0
  33. package/runtime/sandbox/apps/workerpals/src/timeout_policy.ts +24 -0
  34. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +1436 -0
  35. package/runtime/sandbox/apps/workerpals/tsconfig.json +15 -0
  36. package/runtime/sandbox/apps/workerpals/uv.lock +2014 -0
  37. package/runtime/sandbox/bun.lock +2591 -0
  38. package/runtime/sandbox/configs/backend.toml +79 -0
  39. package/runtime/sandbox/configs/default.toml +260 -0
  40. package/runtime/sandbox/configs/dev.toml +2 -0
  41. package/runtime/sandbox/configs/local.example.toml +129 -0
  42. package/runtime/sandbox/package.json +65 -0
  43. package/runtime/sandbox/packages/protocol/README.md +168 -0
  44. package/runtime/sandbox/packages/protocol/package.json +37 -0
  45. package/runtime/sandbox/packages/protocol/scripts/copy-schemas.js +17 -0
  46. package/runtime/sandbox/packages/protocol/src/a2a/README.md +52 -0
  47. package/runtime/sandbox/packages/protocol/src/a2a/mapping.ts +55 -0
  48. package/runtime/sandbox/packages/protocol/src/index.browser.ts +25 -0
  49. package/runtime/sandbox/packages/protocol/src/index.ts +25 -0
  50. package/runtime/sandbox/packages/protocol/src/schemas/approvals.schema.json +6 -0
  51. package/runtime/sandbox/packages/protocol/src/schemas/envelope.schema.json +96 -0
  52. package/runtime/sandbox/packages/protocol/src/schemas/events.schema.json +679 -0
  53. package/runtime/sandbox/packages/protocol/src/schemas/http.schema.json +50 -0
  54. package/runtime/sandbox/packages/protocol/src/types.ts +267 -0
  55. package/runtime/sandbox/packages/protocol/src/validate.browser.ts +154 -0
  56. package/runtime/sandbox/packages/protocol/src/validate.ts +233 -0
  57. package/runtime/sandbox/packages/protocol/src/version.ts +1 -0
  58. package/runtime/sandbox/packages/protocol/tsconfig.json +20 -0
  59. package/runtime/sandbox/packages/shared/package.json +19 -0
  60. package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +400 -0
  61. package/runtime/sandbox/packages/shared/src/client_preflight.ts +286 -0
  62. package/runtime/sandbox/packages/shared/src/communication.ts +313 -0
  63. package/runtime/sandbox/packages/shared/src/config.ts +2180 -0
  64. package/runtime/sandbox/packages/shared/src/config_template_parity.ts +70 -0
  65. package/runtime/sandbox/packages/shared/src/git_backend.ts +205 -0
  66. package/runtime/sandbox/packages/shared/src/index.ts +101 -0
  67. package/runtime/sandbox/packages/shared/src/local_network.ts +101 -0
  68. package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +314 -0
  69. package/runtime/sandbox/packages/shared/src/prompts.ts +64 -0
  70. package/runtime/sandbox/packages/shared/src/repo.ts +134 -0
  71. package/runtime/sandbox/packages/shared/src/session_event_visibility.ts +25 -0
  72. package/runtime/sandbox/packages/shared/src/vision.ts +247 -0
  73. package/runtime/sandbox/packages/shared/tsconfig.json +16 -0
  74. package/runtime/sandbox/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
  75. package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +36 -0
  76. package/runtime/sandbox/prompts/workerpals/commit_message_user_prompt.md +7 -0
  77. package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
  78. package/runtime/sandbox/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
  79. package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -0
  80. package/runtime/sandbox/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
  81. package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
  82. package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
  83. package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
  84. package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
  85. package/runtime/sandbox/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
  86. package/runtime/sandbox/prompts/workerpals/miniswe_timeout_note.md +1 -0
  87. package/runtime/sandbox/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
  88. package/runtime/sandbox/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
  89. package/runtime/sandbox/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
  90. package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
  91. package/runtime/sandbox/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
  92. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
  93. package/runtime/sandbox/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
  94. package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
  95. package/runtime/sandbox/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
  96. package/runtime/sandbox/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
  97. package/runtime/sandbox/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
  98. package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
  99. package/runtime/sandbox/prompts/workerpals/openhands_task_user_prompt.md +6 -0
  100. package/runtime/sandbox/prompts/workerpals/openhands_timeout_note.md +1 -0
  101. package/runtime/sandbox/prompts/workerpals/pr_description.md +42 -0
  102. package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
  103. package/runtime/sandbox/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
  104. package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +115 -0
  105. package/runtime/sandbox/protocol/schemas/approvals.schema.json +6 -0
  106. package/runtime/sandbox/protocol/schemas/envelope.schema.json +96 -0
  107. package/runtime/sandbox/protocol/schemas/events.schema.json +679 -0
  108. package/runtime/sandbox/protocol/schemas/http.schema.json +50 -0
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "HTTPContracts",
4
+ "definitions": {
5
+ "CreateSessionResponse": {
6
+ "type": "object",
7
+ "required": ["sessionId", "protocolVersion"],
8
+ "properties": {
9
+ "sessionId": { "type": "string" },
10
+ "protocolVersion": { "type": "string", "const": "0.1.0" }
11
+ },
12
+ "additionalProperties": false
13
+ },
14
+ "MessageRequest": {
15
+ "type": "object",
16
+ "required": ["text"],
17
+ "properties": {
18
+ "text": { "type": "string" }
19
+ },
20
+ "additionalProperties": false
21
+ },
22
+ "MessageResponse": {
23
+ "type": "object",
24
+ "required": ["ok"],
25
+ "properties": {
26
+ "ok": { "type": "boolean" }
27
+ },
28
+ "additionalProperties": false
29
+ },
30
+ "ApprovalDecisionRequest": {
31
+ "type": "object",
32
+ "required": ["decision"],
33
+ "properties": {
34
+ "decision": {
35
+ "type": "string",
36
+ "enum": ["approve", "deny"]
37
+ }
38
+ },
39
+ "additionalProperties": false
40
+ },
41
+ "ApprovalDecisionResponse": {
42
+ "type": "object",
43
+ "required": ["ok"],
44
+ "properties": {
45
+ "ok": { "type": "boolean" }
46
+ },
47
+ "additionalProperties": false
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,267 @@
1
+ import { PROTOCOL_VERSION } from "./version";
2
+
3
+ // ─── Artifact type (reused across several payloads) ─────────────────────────
4
+ export interface Artifact {
5
+ kind: string;
6
+ uri?: string;
7
+ text?: string;
8
+ }
9
+
10
+ /**
11
+ * Payload types for each event type - enables type narrowing
12
+ */
13
+ export interface EventTypePayloadMap {
14
+ // ── Existing events ───────────────────────────────────────────────────────
15
+ log: { level: "debug" | "info" | "warn" | "error"; message: string };
16
+ scan_result: {
17
+ summary: string;
18
+ filesRead: string[];
19
+ gitStatusPorcelain: string;
20
+ gitDiff: string;
21
+ };
22
+ suggestions: {
23
+ items: Array<{ id: string; title: string; detail: string; effort: "S" | "M" | "L" }>;
24
+ };
25
+ diff_ready: { unifiedDiff: string; diffStat: string; branch: string };
26
+ approval_required: {
27
+ approvalId: string;
28
+ action: "git.commit" | "git.push" | "other";
29
+ summary: string;
30
+ details: Record<string, unknown>;
31
+ };
32
+ approved: { approvalId: string };
33
+ denied: { approvalId: string };
34
+ committed: { branch: string; commitHash: string; message: string };
35
+ assistant_message: { text: string };
36
+ error: { message: string; detail?: string };
37
+ done: { ok: boolean };
38
+
39
+ // ── Multi-agent events ────────────────────────────────────────────────────
40
+ agent_status: {
41
+ agentId: string;
42
+ status: "idle" | "busy" | "error";
43
+ message?: string;
44
+ };
45
+
46
+ // ── Task lifecycle events ─────────────────────────────────────────────────
47
+ task_created: {
48
+ taskId: string;
49
+ title: string;
50
+ description: string;
51
+ createdBy: string;
52
+ priority?: string;
53
+ tags?: string[];
54
+ };
55
+ task_started: { taskId: string };
56
+ task_progress: { taskId: string; message: string; percent?: number };
57
+ task_completed: {
58
+ taskId: string;
59
+ summary: string;
60
+ artifacts?: Artifact[];
61
+ };
62
+ task_failed: { taskId: string; message: string; detail?: string };
63
+
64
+ // ── Tool call/result events ───────────────────────────────────────────────
65
+ tool_call: {
66
+ toolCallId: string;
67
+ taskId?: string;
68
+ tool: string;
69
+ args: Record<string, unknown>;
70
+ requiresApproval?: boolean;
71
+ };
72
+ tool_result: {
73
+ toolCallId: string;
74
+ taskId?: string;
75
+ ok: boolean;
76
+ stdout?: string;
77
+ stderr?: string;
78
+ exitCode?: number;
79
+ artifacts?: Artifact[];
80
+ };
81
+
82
+ // ── Delegation events ─────────────────────────────────────────────────────
83
+ delegate_request: {
84
+ requestId: string;
85
+ toAgentId: string;
86
+ input: Record<string, unknown>;
87
+ };
88
+ delegate_response: {
89
+ requestId: string;
90
+ ok: boolean;
91
+ output?: Record<string, unknown>;
92
+ error?: string;
93
+ };
94
+
95
+ // ── Job queue events ──────────────────────────────────────────────────────
96
+ job_enqueued: {
97
+ jobId: string;
98
+ taskId: string;
99
+ kind: string;
100
+ params: Record<string, unknown>;
101
+ origin?: "user" | "autonomy";
102
+ autonomy?: {
103
+ objectiveId?: string;
104
+ runId?: string;
105
+ snapshotId?: string;
106
+ patternKey?: string;
107
+ };
108
+ };
109
+ job_claimed: { jobId: string; workerId: string };
110
+ job_completed: {
111
+ jobId: string;
112
+ summary?: string;
113
+ artifacts?: Artifact[];
114
+ };
115
+ job_failed: { jobId: string; message: string; detail?: string };
116
+ // ── Streaming / lifecycle events ──────────────────────────────────────
117
+ /** User-facing chat message (from client to server) */
118
+ message: { text: string; intent?: Record<string, unknown> };
119
+
120
+ /** Streaming log line from a worker executing a job */
121
+ job_log: {
122
+ jobId: string;
123
+ stream: "stdout" | "stderr";
124
+ seq: number;
125
+ line: string;
126
+ ts?: string;
127
+ };
128
+
129
+ /** System heartbeat / status beacon */
130
+ status: {
131
+ agentId: string;
132
+ state: "idle" | "busy" | "error" | "shutting_down";
133
+ uptimeMs?: number;
134
+ detail?: string;
135
+ };
136
+
137
+ // —— Autonomous system lifecycle events ————————————————————————————————————————————
138
+ autonomy_cycle_started: {
139
+ runId: string;
140
+ snapshotId: string;
141
+ phase?: string;
142
+ };
143
+ autonomy_candidates_generated: {
144
+ runId: string;
145
+ snapshotId: string;
146
+ candidateCount: number;
147
+ topCandidateIds?: string[];
148
+ };
149
+ autonomy_objective_dispatched: {
150
+ runId: string;
151
+ snapshotId: string;
152
+ objectiveId: string;
153
+ requestId: string;
154
+ patternKey: string;
155
+ origin?: "autonomy";
156
+ };
157
+ autonomy_objective_blocked: {
158
+ runId: string;
159
+ snapshotId: string;
160
+ objectiveId: string;
161
+ reason: string;
162
+ questionId?: string;
163
+ patternKey?: string;
164
+ origin?: "autonomy";
165
+ };
166
+ autonomy_feedback_recorded: {
167
+ objectiveId: string;
168
+ patternKey: string;
169
+ outcome: string;
170
+ success: boolean;
171
+ };
172
+ question_asked: {
173
+ questionId: string;
174
+ objectiveId: string;
175
+ question: string;
176
+ questionType: string;
177
+ };
178
+ question_answered: {
179
+ questionId: string;
180
+ objectiveId: string;
181
+ status: "valid" | "invalid";
182
+ answerSummary?: string;
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Discriminated union of all event payload types
188
+ */
189
+ export type EventPayload = EventTypePayloadMap[keyof EventTypePayloadMap];
190
+
191
+ export type EventType = keyof EventTypePayloadMap;
192
+
193
+ /**
194
+ * EventEnvelope parameterized by event type for type-safe access to payload
195
+ *
196
+ * Routing / meta fields (all optional):
197
+ * - from: originator identifier (e.g. "client", "agent:local1")
198
+ * - to: destination identifier (e.g. "broadcast", "worker:queue:default")
199
+ * - correlationId: threads a whole conversation turn together
200
+ * - parentId: links tool calls/results under their parent task event
201
+ * - turnId: one per user message; groups all downstream events
202
+ */
203
+ export interface EventEnvelope<T extends EventType = EventType> {
204
+ protocolVersion: typeof PROTOCOL_VERSION;
205
+ id: string;
206
+ ts: string; // ISO-8601
207
+ sessionId: string;
208
+ type: T;
209
+ traceId?: string;
210
+
211
+ // Routing / meta
212
+ from?: string;
213
+ to?: string;
214
+ correlationId?: string;
215
+ parentId?: string;
216
+ turnId?: string;
217
+
218
+ payload: EventTypePayloadMap[T];
219
+ }
220
+
221
+ /**
222
+ * Any EventEnvelope - for cases where type is not known at compile time
223
+ */
224
+ export type AnyEventEnvelope = EventEnvelope<EventType>;
225
+
226
+ /**
227
+ * HTTP Request/Response types
228
+ */
229
+ export interface CreateSessionResponse {
230
+ sessionId: string;
231
+ protocolVersion: typeof PROTOCOL_VERSION;
232
+ }
233
+
234
+ export interface MessageRequest {
235
+ text: string;
236
+ intent?: Record<string, unknown>;
237
+ }
238
+
239
+ export interface MessageResponse {
240
+ ok: boolean;
241
+ }
242
+
243
+ export interface ApprovalDecisionRequest {
244
+ decision: "approve" | "deny";
245
+ }
246
+
247
+ export interface ApprovalDecisionResponse {
248
+ ok: boolean;
249
+ }
250
+
251
+ /**
252
+ * Command request (agent-friendly ingest)
253
+ */
254
+ export interface CommandRequest {
255
+ type: EventType;
256
+ payload: Record<string, unknown>;
257
+ from?: string;
258
+ to?: string;
259
+ correlationId?: string;
260
+ turnId?: string;
261
+ parentId?: string;
262
+ }
263
+
264
+ export interface CommandResponse {
265
+ ok: boolean;
266
+ eventId?: string;
267
+ }
@@ -0,0 +1,154 @@
1
+ import Ajv from "ajv";
2
+ import addFormats from "ajv-formats";
3
+ import envelopeSchema from "./schemas/envelope.schema.json";
4
+ import eventsSchema from "./schemas/events.schema.json";
5
+
6
+ const ajv = new Ajv({ strict: true });
7
+ addFormats(ajv);
8
+
9
+ // Register schemas to help $ref linking when present
10
+ try {
11
+ ajv.addSchema(envelopeSchema as object, "envelope.schema.json");
12
+ ajv.addSchema(eventsSchema as object, "events.schema.json");
13
+ } catch (_e) {
14
+ // ignore addSchema failures; we'll still compile below
15
+ }
16
+
17
+ const validateEnvelopeBase = ajv.compile(envelopeSchema as unknown as object);
18
+ const validateEventPayload = ajv.compile(eventsSchema as unknown as object);
19
+
20
+ const validateMessageRequestSchema = ajv.compile({
21
+ type: "object",
22
+ required: ["text"],
23
+ properties: { text: { type: "string" }, intent: { type: "object", additionalProperties: true } },
24
+ additionalProperties: false,
25
+ });
26
+
27
+ const validateMessageResponseSchema = ajv.compile({
28
+ type: "object",
29
+ required: ["ok"],
30
+ properties: { ok: { type: "boolean" } },
31
+ additionalProperties: false,
32
+ });
33
+
34
+ const validateApprovalDecisionRequestSchema = ajv.compile({
35
+ type: "object",
36
+ required: ["decision"],
37
+ properties: { decision: { type: "string", enum: ["approve", "deny"] } },
38
+ additionalProperties: false,
39
+ });
40
+
41
+ const validateApprovalDecisionResponseSchema = ajv.compile({
42
+ type: "object",
43
+ required: ["ok"],
44
+ properties: { ok: { type: "boolean" } },
45
+ additionalProperties: false,
46
+ });
47
+
48
+ export interface ValidationResult {
49
+ ok: boolean;
50
+ errors?: string[];
51
+ }
52
+
53
+ export function validateEventEnvelope(data: unknown): ValidationResult {
54
+ const baseValid = validateEnvelopeBase(data);
55
+ if (!baseValid) {
56
+ const errors = (validateEnvelopeBase.errors ?? []).map((e) =>
57
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
58
+ );
59
+ return { ok: false, errors };
60
+ }
61
+
62
+ // Validate only the `{ type, payload }` pairing against the events schema.
63
+ const maybe = data as any;
64
+ const pair = { type: maybe?.type, payload: maybe?.payload };
65
+
66
+ const payloadValid = validateEventPayload(pair);
67
+ if (!payloadValid) {
68
+ const errors = (validateEventPayload.errors ?? []).map((e) =>
69
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
70
+ );
71
+ return { ok: false, errors };
72
+ }
73
+
74
+ return { ok: true };
75
+ }
76
+
77
+ export function validateMessageRequest(data: unknown): ValidationResult {
78
+ const valid = validateMessageRequestSchema(data);
79
+ const errors = valid
80
+ ? undefined
81
+ : (validateMessageRequestSchema.errors ?? []).map((e) =>
82
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
83
+ );
84
+ return { ok: valid, errors };
85
+ }
86
+
87
+ export function validateMessageResponse(data: unknown): ValidationResult {
88
+ const valid = validateMessageResponseSchema(data);
89
+ const errors = valid
90
+ ? undefined
91
+ : (validateMessageResponseSchema.errors ?? []).map((e) =>
92
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
93
+ );
94
+ return { ok: valid, errors };
95
+ }
96
+
97
+ export function validateCommandRequest(data: unknown): ValidationResult {
98
+ const allEventTypes = [
99
+ "log",
100
+ "scan_result",
101
+ "suggestions",
102
+ "diff_ready",
103
+ "approval_required",
104
+ "approved",
105
+ "denied",
106
+ "committed",
107
+ "assistant_message",
108
+ "error",
109
+ "done",
110
+ "agent_status",
111
+ "task_created",
112
+ "task_started",
113
+ "task_progress",
114
+ "task_completed",
115
+ "task_failed",
116
+ "tool_call",
117
+ "tool_result",
118
+ "delegate_request",
119
+ "delegate_response",
120
+ "job_enqueued",
121
+ "job_claimed",
122
+ "job_completed",
123
+ "job_failed",
124
+ "message",
125
+ "job_log",
126
+ "status",
127
+ ];
128
+ const d = data as any;
129
+ if (!d || typeof d !== "object") return { ok: false, errors: ["Expected object"] };
130
+ if (!d.type || !allEventTypes.includes(d.type)) return { ok: false, errors: ["Invalid type"] };
131
+ if (!d.payload || typeof d.payload !== "object")
132
+ return { ok: false, errors: ["Invalid payload"] };
133
+ return { ok: true };
134
+ }
135
+
136
+ export function validateApprovalDecisionRequest(data: unknown): ValidationResult {
137
+ const valid = validateApprovalDecisionRequestSchema(data);
138
+ const errors = valid
139
+ ? undefined
140
+ : (validateApprovalDecisionRequestSchema.errors ?? []).map((e) =>
141
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
142
+ );
143
+ return { ok: valid, errors };
144
+ }
145
+
146
+ export function validateApprovalDecisionResponse(data: unknown): ValidationResult {
147
+ const valid = validateApprovalDecisionResponseSchema(data);
148
+ const errors = valid
149
+ ? undefined
150
+ : (validateApprovalDecisionResponseSchema.errors ?? []).map((e) =>
151
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
152
+ );
153
+ return { ok: valid, errors };
154
+ }
@@ -0,0 +1,233 @@
1
+ import Ajv from "ajv";
2
+ import addFormats from "ajv-formats";
3
+ import { readFileSync } from "fs";
4
+ import { fileURLToPath } from "url";
5
+ import { dirname, join } from "path";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const ajv = new Ajv({ strict: true });
9
+
10
+ // Add format validators for date-time, etc.
11
+ addFormats(ajv);
12
+
13
+ /**
14
+ * Expected runtime layout (after `npm run build` / `bun --cwd packages/protocol build`):
15
+ *
16
+ * packages/protocol/dist/index.js
17
+ * packages/protocol/dist/validate.js
18
+ * packages/protocol/dist/schemas/*.json
19
+ *
20
+ * The loader below prefers `dist/schemas` when running compiled JS and falls
21
+ * back to `../src/schemas` during development. The `scripts/copy-schemas.js`
22
+ * build step copies `src/schemas` into `dist/schemas` to satisfy runtime loads.
23
+ */
24
+
25
+ /**
26
+ * Load schema from file. Deterministic order:
27
+ * 1. Built/runtime: `dist/schemas` (when running compiled JS)
28
+ * 2. Development: `src/schemas` (when running from source)
29
+ */
30
+ function loadSchema(filename: string): Record<string, unknown> {
31
+ const distSchemasPath = join(__dirname, "schemas", filename); // dist/schemas when compiled
32
+ const srcSchemasPath = join(__dirname, "..", "src", "schemas", filename); // src/schemas during development
33
+ const envSchemasDir = String(process.env.PUSHPALS_PROTOCOL_SCHEMAS_DIR ?? "").trim();
34
+ const envSchemasPath = envSchemasDir ? join(envSchemasDir, filename) : "";
35
+ const candidates = [distSchemasPath, srcSchemasPath, envSchemasPath].filter(Boolean);
36
+
37
+ for (const pathValue of candidates) {
38
+ try {
39
+ return JSON.parse(readFileSync(pathValue, "utf-8"));
40
+ } catch {
41
+ // try next path
42
+ }
43
+ }
44
+
45
+ throw new Error(
46
+ `Failed to load schema ${filename}. Expected at dist/schemas (build), src/schemas (dev), or PUSHPALS_PROTOCOL_SCHEMAS_DIR.`,
47
+ );
48
+ }
49
+
50
+ // Load schemas
51
+ const envelopeSchema = loadSchema("envelope.schema.json");
52
+ const eventsSchema = loadSchema("events.schema.json");
53
+
54
+ // Register schemas with AJV (helps with $ref resolution across files)
55
+ try {
56
+ ajv.addSchema(envelopeSchema as object, "envelope.schema.json");
57
+ ajv.addSchema(eventsSchema as object, "events.schema.json");
58
+ } catch (_e) {
59
+ // addSchema may throw in strict modes for malformed schemas; compilation below
60
+ // will still attempt to compile standalone validators.
61
+ }
62
+
63
+ // Compile validators
64
+ const validateEnvelopeBase = ajv.compile(envelopeSchema as object);
65
+ const validateEventPayload = ajv.compile(eventsSchema as object);
66
+ const validateMessageRequestSchema = ajv.compile({
67
+ type: "object",
68
+ required: ["text"],
69
+ properties: {
70
+ text: { type: "string" },
71
+ intent: { type: "object", additionalProperties: true },
72
+ },
73
+ additionalProperties: false,
74
+ });
75
+ const validateMessageResponseSchema = ajv.compile({
76
+ type: "object",
77
+ required: ["ok"],
78
+ properties: {
79
+ ok: { type: "boolean" },
80
+ },
81
+ additionalProperties: false,
82
+ });
83
+ const validateApprovalDecisionRequestSchema = ajv.compile({
84
+ type: "object",
85
+ required: ["decision"],
86
+ properties: {
87
+ decision: {
88
+ type: "string",
89
+ enum: ["approve", "deny"],
90
+ },
91
+ },
92
+ additionalProperties: false,
93
+ });
94
+ const validateApprovalDecisionResponseSchema = ajv.compile({
95
+ type: "object",
96
+ required: ["ok"],
97
+ properties: {
98
+ ok: { type: "boolean" },
99
+ },
100
+ additionalProperties: false,
101
+ });
102
+
103
+ export interface ValidationResult {
104
+ ok: boolean;
105
+ errors?: string[];
106
+ }
107
+
108
+ /**
109
+ * Validate an EventEnvelope against the full schema
110
+ */
111
+ export function validateEventEnvelope(data: unknown): ValidationResult {
112
+ const baseValid = validateEnvelopeBase(data);
113
+ if (!baseValid) {
114
+ const errors = (validateEnvelopeBase.errors ?? []).map((e) =>
115
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
116
+ );
117
+ return { ok: false, errors };
118
+ }
119
+
120
+ // Validate only the `{ type, payload }` pairing against the events schema.
121
+ // This ensures branch validation focuses on the event discriminant and
122
+ // its payload shape, while envelope-level fields are handled above.
123
+ const maybe = data as any;
124
+ const pair = { type: maybe?.type, payload: maybe?.payload };
125
+
126
+ const payloadValid = validateEventPayload(pair);
127
+ if (!payloadValid) {
128
+ const errors = (validateEventPayload.errors ?? []).map((e) =>
129
+ `${e.instancePath || "/"} ${e.message ?? ""}`.trim(),
130
+ );
131
+ return { ok: false, errors };
132
+ }
133
+
134
+ return { ok: true };
135
+ }
136
+
137
+ export function validateMessageRequest(data: unknown): ValidationResult {
138
+ const valid = validateMessageRequestSchema(data);
139
+ return {
140
+ ok: valid,
141
+ errors: valid ? undefined : ajv.errorsText(validateMessageRequestSchema.errors).split(", "),
142
+ };
143
+ }
144
+
145
+ export function validateMessageResponse(data: unknown): ValidationResult {
146
+ const valid = validateMessageResponseSchema(data);
147
+ return {
148
+ ok: valid,
149
+ errors: valid ? undefined : ajv.errorsText(validateMessageResponseSchema.errors).split(", "),
150
+ };
151
+ }
152
+
153
+ export function validateApprovalDecisionRequest(data: unknown): ValidationResult {
154
+ const valid = validateApprovalDecisionRequestSchema(data);
155
+ return {
156
+ ok: valid,
157
+ errors: valid
158
+ ? undefined
159
+ : ajv.errorsText(validateApprovalDecisionRequestSchema.errors).split(", "),
160
+ };
161
+ }
162
+
163
+ export function validateApprovalDecisionResponse(data: unknown): ValidationResult {
164
+ const valid = validateApprovalDecisionResponseSchema(data);
165
+ return {
166
+ ok: valid,
167
+ errors: valid
168
+ ? undefined
169
+ : ajv.errorsText(validateApprovalDecisionResponseSchema.errors).split(", "),
170
+ };
171
+ }
172
+
173
+ // Command request schema (agent-friendly ingest)
174
+ const allEventTypes = [
175
+ "log",
176
+ "scan_result",
177
+ "suggestions",
178
+ "diff_ready",
179
+ "approval_required",
180
+ "approved",
181
+ "denied",
182
+ "committed",
183
+ "assistant_message",
184
+ "error",
185
+ "done",
186
+ "agent_status",
187
+ "task_created",
188
+ "task_started",
189
+ "task_progress",
190
+ "task_completed",
191
+ "task_failed",
192
+ "tool_call",
193
+ "tool_result",
194
+ "delegate_request",
195
+ "delegate_response",
196
+ "job_enqueued",
197
+ "job_claimed",
198
+ "job_completed",
199
+ "job_failed",
200
+ "message",
201
+ "job_log",
202
+ "status",
203
+ "autonomy_cycle_started",
204
+ "autonomy_candidates_generated",
205
+ "autonomy_objective_dispatched",
206
+ "autonomy_objective_blocked",
207
+ "autonomy_feedback_recorded",
208
+ "question_asked",
209
+ "question_answered",
210
+ ];
211
+
212
+ const validateCommandRequestSchema = ajv.compile({
213
+ type: "object",
214
+ required: ["type", "payload"],
215
+ properties: {
216
+ type: { type: "string", enum: allEventTypes },
217
+ payload: { type: "object", additionalProperties: true },
218
+ from: { type: "string" },
219
+ to: { type: "string" },
220
+ correlationId: { type: "string" },
221
+ turnId: { type: "string" },
222
+ parentId: { type: "string" },
223
+ },
224
+ additionalProperties: false,
225
+ });
226
+
227
+ export function validateCommandRequest(data: unknown): ValidationResult {
228
+ const valid = validateCommandRequestSchema(data);
229
+ return {
230
+ ok: valid,
231
+ errors: valid ? undefined : ajv.errorsText(validateCommandRequestSchema.errors).split(", "),
232
+ };
233
+ }
@@ -0,0 +1 @@
1
+ export const PROTOCOL_VERSION = "0.1.0";