pi-harness-runtime 1.1.429 → 1.1.430

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 (126) hide show
  1. package/package.json +1 -1
  2. package/packages/a2a-adapter/package.json +1 -1
  3. package/packages/agent-policy/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/codex-watcher/package.json +1 -1
  18. package/packages/config-capture/package.json +1 -1
  19. package/packages/connection-runtime/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/contracts/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/durable-review/package.json +1 -1
  30. package/packages/e2e-tester/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-sink/package.json +1 -1
  35. package/packages/event-store/package.json +1 -1
  36. package/packages/experience-replay/package.json +1 -1
  37. package/packages/feedback-collector/package.json +1 -1
  38. package/packages/file-copy-helper/package.json +1 -1
  39. package/packages/framework-detector/package.json +1 -1
  40. package/packages/framework-plugin-sdk/package.json +1 -1
  41. package/packages/frappe-plugin/package.json +1 -1
  42. package/packages/generic-web-plugin/package.json +1 -1
  43. package/packages/health-monitor/package.json +1 -1
  44. package/packages/intent-analyzer/package.json +1 -1
  45. package/packages/jev-judge/package.json +1 -1
  46. package/packages/knowledge-graph/package.json +1 -1
  47. package/packages/knowledge-retrieval/package.json +1 -1
  48. package/packages/laravel-plugin/package.json +1 -1
  49. package/packages/learning-engine/package.json +1 -1
  50. package/packages/logging/package.json +1 -1
  51. package/packages/mcp-adapter/package.json +1 -1
  52. package/packages/memory-engine/package.json +1 -1
  53. package/packages/milestone-manager/package.json +1 -1
  54. package/packages/model-registry/package.json +1 -1
  55. package/packages/nextjs-plugin/package.json +1 -1
  56. package/packages/notification/package.json +1 -1
  57. package/packages/observability/package.json +1 -1
  58. package/packages/okf-indexer/package.json +1 -1
  59. package/packages/paperclip-integration/package.json +1 -1
  60. package/packages/performance-optimizer/package.json +1 -1
  61. package/packages/permission-mode/package.json +1 -1
  62. package/packages/privilege-broker/package.json +1 -1
  63. package/packages/project-analyzer/package.json +1 -1
  64. package/packages/project-bootstrap/package.json +1 -1
  65. package/packages/projection-engine/package.json +1 -1
  66. package/packages/prompt-compiler/package.json +1 -1
  67. package/packages/prompt-versioning/package.json +1 -1
  68. package/packages/provider-adapter-sdk/package.json +1 -1
  69. package/packages/provider-router/package.json +1 -1
  70. package/packages/provider-selector/package.json +1 -1
  71. package/packages/providers/package.json +1 -1
  72. package/packages/qdrant-skills/package.json +1 -1
  73. package/packages/quota-manager/package.json +1 -1
  74. package/packages/rate-limiter/package.json +1 -1
  75. package/packages/react-vite-plugin/package.json +1 -1
  76. package/packages/relay/package.json +1 -1
  77. package/packages/release-manager/package.json +1 -1
  78. package/packages/requirement-compiler/package.json +1 -1
  79. package/packages/runtime/dist/runtime-api.d.ts +61 -6
  80. package/packages/runtime/dist/runtime-api.d.ts.map +1 -1
  81. package/packages/runtime/dist/runtime-api.js +83 -30
  82. package/packages/runtime/dist/runtime-api.js.map +1 -1
  83. package/packages/runtime/package.json +1 -1
  84. package/packages/runtime/src/runtime-api.ts +168 -52
  85. package/packages/runtime/test/runtime-api.test.ts +169 -0
  86. package/packages/scheduler/package.json +1 -1
  87. package/packages/scheduler-adapter/package.json +1 -1
  88. package/packages/session/package.json +1 -1
  89. package/packages/session-api/package.json +1 -1
  90. package/packages/session-export/package.json +1 -1
  91. package/packages/shared-context/package.json +1 -1
  92. package/packages/skill-mcp-client/package.json +1 -1
  93. package/packages/skill-registry/package.json +1 -1
  94. package/packages/skills/package.json +1 -1
  95. package/packages/sprint-planner/package.json +1 -1
  96. package/packages/subscription-engine/package.json +1 -1
  97. package/packages/task-compiler/package.json +1 -1
  98. package/packages/tencentdb-memory/package.json +1 -1
  99. package/packages/tencentdb-sync/package.json +1 -1
  100. package/packages/test-data-generator/package.json +1 -1
  101. package/packages/test-generator/package.json +1 -1
  102. package/packages/todo-bd-sync/package.json +1 -1
  103. package/packages/token-estimation/package.json +1 -1
  104. package/packages/token-optimizer/package.json +1 -1
  105. package/packages/tooling-preference/package.json +1 -1
  106. package/packages/trajectory/package.json +1 -1
  107. package/packages/tui/package.json +1 -1
  108. package/packages/two-phase-review/package.json +1 -1
  109. package/packages/types/package.json +1 -1
  110. package/packages/voice-api/package.json +1 -1
  111. package/packages/webmcp-tester/package.json +1 -1
  112. package/packages/wiki/package.json +1 -1
  113. package/packages/workflow-events/package.json +1 -1
  114. package/packages/workspace-scanner/package.json +1 -1
  115. package/packages/worktree/package.json +1 -1
  116. package/packages/write-review/package.json +1 -1
  117. package/scripts/typecheck-packages.mjs +1 -1
  118. package/harness/task-graph.d.ts +0 -51
  119. package/packages/runtime/command-executor.ts +0 -523
  120. package/packages/runtime/index.ts +0 -50
  121. package/packages/runtime/local-runtime-agent.ts +0 -480
  122. package/packages/runtime/policy-engine.ts +0 -813
  123. package/packages/runtime/runtime-api.ts +0 -665
  124. package/packages/runtime/workspace-manager.ts +0 -591
  125. package/packages/types/src/ai-providers.js +0 -66
  126. package/packages/types/src/runtime-types.js +0 -3
@@ -25,9 +25,86 @@ import { createServer, type Server, type IncomingMessage } from "node:http";
25
25
  import { WebSocketServer, WebSocket } from "ws";
26
26
  import { URL } from "node:url";
27
27
  import { EventEmitter } from "node:events";
28
- import type { JobStateMachine } from "../../harness/job-state-machine.js";
29
- import type { TaskGraphManager } from "../../../harness/task-graph.js";
30
- import type { CheckpointManager } from "../../harness/job-state-machine.js";
28
+ import type {
29
+ JobStatus,
30
+ RuntimeCheckpoint,
31
+ RuntimeEvent,
32
+ RuntimeTask,
33
+ } from "../../types/dist/runtime-types.js";
34
+
35
+ /**
36
+ * The outcome of a job transition, as reported by the registered machine.
37
+ */
38
+ export interface JobTransitionResult {
39
+ success: boolean;
40
+ checkpoint?: RuntimeCheckpoint;
41
+ error?: string;
42
+ }
43
+
44
+ /**
45
+ * The job actions the API exposes.
46
+ *
47
+ * The states are the ones the job state machine actually has:
48
+ * `paused_quota` is its only paused state and a paused job returns to
49
+ * `running`. There is no plain `paused` status, so the API must not
50
+ * pretend there is one.
51
+ */
52
+ const JOB_ACTIONS: Record<
53
+ string,
54
+ { status: JobStatus; message: string; event: string }
55
+ > = {
56
+ start: { status: "running", message: "Job started", event: "job.started" },
57
+ pause: {
58
+ status: "paused_quota",
59
+ message: "Job paused",
60
+ event: "job.paused",
61
+ },
62
+ resume: { status: "running", message: "Job resumed", event: "job.resumed" },
63
+ cancel: {
64
+ status: "cancelled",
65
+ message: "Job cancelled",
66
+ event: "job.cancelled",
67
+ },
68
+ };
69
+
70
+ /**
71
+ * The job state machine the harness registers.
72
+ *
73
+ * The runtime API does not implement a job: it stores the machine the
74
+ * harness already runs and reports on it. The package therefore describes
75
+ * the shape it uses instead of importing the harness - the harness is the
76
+ * app root, a package may not reach into it, and the import also dragged
77
+ * the whole harness program into this package's compilation.
78
+ */
79
+ export interface JobStateMachineLike {
80
+ getStatusSummary(): {
81
+ status: JobStatus;
82
+ isTerminal: boolean;
83
+ canResume: boolean;
84
+ } | null;
85
+ getCheckpoint(): RuntimeCheckpoint | null | Promise<RuntimeCheckpoint | null>;
86
+ resumeJob(jobId: string): Promise<JobTransitionResult>;
87
+ transition(
88
+ to: JobStatus,
89
+ data?: Partial<RuntimeCheckpoint>,
90
+ ): Promise<JobTransitionResult>;
91
+ }
92
+
93
+ /**
94
+ * The task graph the harness registers.
95
+ */
96
+ export interface TaskGraphLike {
97
+ getAllTasks(): RuntimeTask[];
98
+ }
99
+
100
+ /**
101
+ * The checkpoint manager the harness registers.
102
+ */
103
+ export interface CheckpointManagerLike {
104
+ save(checkpoint: RuntimeCheckpoint): Promise<void>;
105
+ load(jobId: string): Promise<RuntimeCheckpoint | null>;
106
+ appendEvent(jobId: string, event: RuntimeEvent): Promise<void>;
107
+ }
31
108
 
32
109
  export interface RuntimeApiConfig {
33
110
  /** Port to listen on (default: 3849) */
@@ -107,9 +184,9 @@ export class RuntimeApi extends EventEmitter {
107
184
  private wsClients: Set<WebSocket> = new Set();
108
185
  private isRunning = false;
109
186
  private startTime: number = 0;
110
- private jobMachines: Map<string, JobStateMachine> = new Map();
111
- private taskGraphs: Map<string, TaskGraphManager> = new Map();
112
- private checkpoints: Map<string, CheckpointManager> = new Map();
187
+ private jobMachines: Map<string, JobStateMachineLike> = new Map();
188
+ private taskGraphs: Map<string, TaskGraphLike> = new Map();
189
+ private checkpoints: Map<string, CheckpointManagerLike> = new Map();
113
190
  private requestCounter = 0;
114
191
 
115
192
  constructor(config: RuntimeApiConfig = {}) {
@@ -118,6 +195,18 @@ export class RuntimeApi extends EventEmitter {
118
195
  this.startTime = Date.now();
119
196
  }
120
197
 
198
+ /**
199
+ * The port the API listens on.
200
+ *
201
+ * The operating system picks the port when the API was started on port
202
+ * 0, so the running port is not always the configured one.
203
+ */
204
+ getPort(): number | null {
205
+ const address = this.server?.address();
206
+ if (!address || typeof address === "string") return null;
207
+ return address.port;
208
+ }
209
+
121
210
  /**
122
211
  * Start the Runtime API server
123
212
  */
@@ -212,21 +301,24 @@ export class RuntimeApi extends EventEmitter {
212
301
  /**
213
302
  * Register a job machine
214
303
  */
215
- registerJob(jobId: string, machine: JobStateMachine): void {
304
+ registerJob(jobId: string, machine: JobStateMachineLike): void {
216
305
  this.jobMachines.set(jobId, machine);
217
306
  }
218
307
 
219
308
  /**
220
309
  * Register a task graph
221
310
  */
222
- registerTaskGraph(jobId: string, graph: TaskGraphManager): void {
311
+ registerTaskGraph(jobId: string, graph: TaskGraphLike): void {
223
312
  this.taskGraphs.set(jobId, graph);
224
313
  }
225
314
 
226
315
  /**
227
316
  * Register a checkpoint manager
228
317
  */
229
- registerCheckpointManager(jobId: string, manager: CheckpointManager): void {
318
+ registerCheckpointManager(
319
+ jobId: string,
320
+ manager: CheckpointManagerLike,
321
+ ): void {
230
322
  this.checkpoints.set(jobId, manager);
231
323
  }
232
324
 
@@ -315,22 +407,32 @@ export class RuntimeApi extends EventEmitter {
315
407
  res: import("node:http").ServerResponse,
316
408
  ): void {
317
409
  const origin = req.headers.origin;
318
- const allowedOrigins = this.config.corsOrigins ?? ["*"];
410
+ if (!origin) {
411
+ // Only a browser request carries an Origin header, and only a
412
+ // browser request is affected by the CORS headers.
413
+ return;
414
+ }
319
415
 
416
+ const allowedOrigins = this.config.corsOrigins ?? ["*"];
320
417
  if (
321
- allowedOrigins.includes("*") ||
322
- (origin && allowedOrigins.includes(origin))
418
+ !allowedOrigins.includes("*") &&
419
+ !allowedOrigins.includes(origin)
323
420
  ) {
324
- res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
325
- res.setHeader(
326
- "Access-Control-Allow-Methods",
327
- "GET, POST, PUT, DELETE, OPTIONS",
328
- );
329
- res.setHeader(
330
- "Access-Control-Allow-Headers",
331
- "Content-Type, Authorization, X-Api-Key",
332
- );
421
+ return;
333
422
  }
423
+
424
+ // The request origin is reflected instead of the wildcard: the API
425
+ // never enables credentialed requests, and a reflected origin keeps
426
+ // the response usable for a browser client.
427
+ res.setHeader("Access-Control-Allow-Origin", origin);
428
+ res.setHeader(
429
+ "Access-Control-Allow-Methods",
430
+ "GET, POST, PUT, DELETE, OPTIONS",
431
+ );
432
+ res.setHeader(
433
+ "Access-Control-Allow-Headers",
434
+ "Content-Type, Authorization, X-Api-Key",
435
+ );
334
436
  }
335
437
 
336
438
  private async handleRequest(
@@ -400,11 +502,7 @@ export class RuntimeApi extends EventEmitter {
400
502
  const summary = machine.getStatusSummary();
401
503
  if (summary) {
402
504
  if (summary.status === "running") status.jobs.active++;
403
- else if (
404
- summary.status === "paused" ||
405
- summary.status === "paused_quota"
406
- )
407
- status.jobs.paused++;
505
+ else if (summary.status === "paused_quota") status.jobs.paused++;
408
506
  else if (summary.isTerminal) status.jobs.completed++;
409
507
  }
410
508
  }
@@ -498,7 +596,7 @@ export class RuntimeApi extends EventEmitter {
498
596
  jobId: string,
499
597
  method: string,
500
598
  url: URL,
501
- req: IncomingMessage,
599
+ _req: IncomingMessage,
502
600
  res: import("node:http").ServerResponse,
503
601
  requestId: string,
504
602
  ): Promise<void> {
@@ -550,43 +648,61 @@ export class RuntimeApi extends EventEmitter {
550
648
  const action = url.searchParams.get("action");
551
649
 
552
650
  switch (method) {
553
- case "POST":
554
- if (action === "start") {
555
- await machine.resumeJob(jobId);
556
- this.broadcast("job.started", { jobId });
557
- this.sendJson(res, 200, { message: "Job started", jobId }, requestId);
558
- } else if (action === "pause") {
559
- const result = await machine.transition("paused");
560
- this.broadcast("job.paused", { jobId });
561
- this.sendJson(
562
- res,
563
- 200,
564
- { message: "Job paused", jobId, result },
565
- requestId,
566
- );
567
- } else if (action === "resume") {
568
- await machine.resumeJob(jobId);
569
- this.broadcast("job.resumed", { jobId });
570
- this.sendJson(res, 200, { message: "Job resumed", jobId }, requestId);
571
- } else if (action === "cancel") {
572
- const result = await machine.transition("cancelled");
573
- this.broadcast("job.cancelled", { jobId });
651
+ case "POST": {
652
+ const target = JOB_ACTIONS[action ?? ""];
653
+ if (!target) {
654
+ this.sendJson(res, 400, { error: "Unknown action" }, requestId);
655
+ break;
656
+ }
657
+
658
+ const result = await this.moveJob(machine, jobId, target.status);
659
+ if (!result.success) {
574
660
  this.sendJson(
575
661
  res,
576
- 200,
577
- { message: "Job cancelled", jobId, result },
662
+ 409,
663
+ {
664
+ error: result.error ?? `Cannot ${target.message.toLowerCase()}`,
665
+ jobId,
666
+ },
578
667
  requestId,
579
668
  );
580
- } else {
581
- this.sendJson(res, 400, { error: "Unknown action" }, requestId);
669
+ break;
582
670
  }
671
+
672
+ this.broadcast(target.event, { jobId });
673
+ this.sendJson(res, 200, { message: target.message, jobId }, requestId);
583
674
  break;
675
+ }
584
676
 
585
677
  default:
586
678
  this.sendJson(res, 405, { error: "Method not allowed" }, requestId);
587
679
  }
588
680
  }
589
681
 
682
+ /**
683
+ * Move a job to a new state.
684
+ *
685
+ * The machine keeps the active checkpoint in memory, so it has to be
686
+ * activated for the job before it can transition. The transition itself
687
+ * validates the move against the state machine's rules and reports the
688
+ * outcome, so a refused move never becomes a false success.
689
+ */
690
+ private async moveJob(
691
+ machine: JobStateMachineLike,
692
+ jobId: string,
693
+ to: JobStatus,
694
+ ): Promise<JobTransitionResult> {
695
+ const activated = await machine.resumeJob(jobId);
696
+ if (!activated.success) {
697
+ return {
698
+ success: false,
699
+ error: activated.error ?? `Job ${jobId} is not available`,
700
+ };
701
+ }
702
+
703
+ return machine.transition(to);
704
+ }
705
+
590
706
  private async handleGetQuota(
591
707
  res: import("node:http").ServerResponse,
592
708
  requestId: string,
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Runtime API tests — the job actions and the states they use.
3
+ *
4
+ * The API stores the job machine the harness registers and moves the job
5
+ * through the state machine. The states are the ones the state machine
6
+ * actually has: `paused_quota` is its only paused state, and there is no
7
+ * plain `paused` status.
8
+ */
9
+
10
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
11
+ import {
12
+ RuntimeApi,
13
+ type JobStateMachineLike,
14
+ type JobTransitionResult,
15
+ } from "../src/runtime-api.js";
16
+ import type {
17
+ JobStatus,
18
+ RuntimeCheckpoint,
19
+ } from "../../types/dist/runtime-types.js";
20
+
21
+ /** The states the fake machine accepts. */
22
+ const ACCEPTED: JobStatus[] = ["running", "paused_quota", "cancelled"];
23
+
24
+ /** The envelope every API response uses. */
25
+ interface ApiResponseBody<T> {
26
+ success: boolean;
27
+ data?: T;
28
+ error?: string;
29
+ }
30
+
31
+ /** Read the JSON body of a response. */
32
+ async function readJson<T>(res: Response): Promise<ApiResponseBody<T>> {
33
+ return (await res.json()) as ApiResponseBody<T>;
34
+ }
35
+
36
+ /** A job machine that records the moves the API asks for. */
37
+ class FakeMachine implements JobStateMachineLike {
38
+ status: JobStatus = "running";
39
+ readonly moves: JobStatus[] = [];
40
+
41
+ getStatusSummary() {
42
+ return {
43
+ status: this.status,
44
+ isTerminal: this.status === "cancelled",
45
+ canResume: this.status === "paused_quota",
46
+ };
47
+ }
48
+
49
+ getCheckpoint(): RuntimeCheckpoint {
50
+ return {
51
+ version: 1,
52
+ jobId: "job-1",
53
+ status: this.status,
54
+ requirement: "do the thing",
55
+ createdAt: "2026-01-01T00:00:00.000Z",
56
+ updatedAt: "2026-01-01T00:00:00.000Z",
57
+ };
58
+ }
59
+
60
+ async resumeJob(): Promise<JobTransitionResult> {
61
+ return { success: true };
62
+ }
63
+
64
+ async transition(to: JobStatus): Promise<JobTransitionResult> {
65
+ this.moves.push(to);
66
+ if (!ACCEPTED.includes(to)) {
67
+ return { success: false, error: `Invalid transition to ${to}` };
68
+ }
69
+ this.status = to;
70
+ return { success: true };
71
+ }
72
+ }
73
+
74
+ describe("RuntimeApi job actions", () => {
75
+ let api: RuntimeApi;
76
+ let machine: FakeMachine;
77
+ let base: string;
78
+
79
+ beforeEach(async () => {
80
+ api = new RuntimeApi({ port: 0, host: "127.0.0.1" });
81
+ machine = new FakeMachine();
82
+ api.registerJob("job-1", machine);
83
+ await api.start();
84
+ base = `http://127.0.0.1:${api.getPort()}`;
85
+ });
86
+
87
+ afterEach(async () => {
88
+ await api.stop();
89
+ });
90
+
91
+ it("pauses a job into the state the machine has", async () => {
92
+ const res = await fetch(`${base}/jobs/job-1?action=pause`, {
93
+ method: "POST",
94
+ });
95
+
96
+ expect(res.status).toBe(200);
97
+ expect(machine.moves).toEqual(["paused_quota"]);
98
+ expect(machine.status).toBe("paused_quota");
99
+ });
100
+
101
+ it("resumes a paused job", async () => {
102
+ await fetch(`${base}/jobs/job-1?action=pause`, { method: "POST" });
103
+ machine.moves.length = 0;
104
+
105
+ const res = await fetch(`${base}/jobs/job-1?action=resume`, {
106
+ method: "POST",
107
+ });
108
+
109
+ expect(res.status).toBe(200);
110
+ expect(machine.moves).toEqual(["running"]);
111
+ expect(machine.status).toBe("running");
112
+ });
113
+
114
+ it("cancels a job", async () => {
115
+ const res = await fetch(`${base}/jobs/job-1?action=cancel`, {
116
+ method: "POST",
117
+ });
118
+
119
+ expect(res.status).toBe(200);
120
+ expect(machine.status).toBe("cancelled");
121
+ });
122
+
123
+ it("reports a refused move instead of a false success", async () => {
124
+ const refusing = new FakeMachine();
125
+ refusing.transition = async (to: JobStatus) => {
126
+ refusing.moves.push(to);
127
+ return { success: false, error: "the state machine refused the move" };
128
+ };
129
+ api.registerJob("job-2", refusing);
130
+
131
+ const res = await fetch(`${base}/jobs/job-2?action=pause`, {
132
+ method: "POST",
133
+ });
134
+
135
+ expect(res.status).toBe(409);
136
+ const body = await readJson<never>(res);
137
+ expect(body.error).toBe("the state machine refused the move");
138
+ });
139
+
140
+ it("rejects an unknown action", async () => {
141
+ const res = await fetch(`${base}/jobs/job-1?action=explode`, {
142
+ method: "POST",
143
+ });
144
+
145
+ expect(res.status).toBe(400);
146
+ expect(machine.moves).toEqual([]);
147
+ });
148
+
149
+ it("counts a paused job as paused", async () => {
150
+ await fetch(`${base}/jobs/job-1?action=pause`, { method: "POST" });
151
+
152
+ const res = await fetch(`${base}/status`);
153
+ const body = await readJson<{
154
+ jobs: { total: number; active: number; paused: number; completed: number };
155
+ }>(res);
156
+
157
+ expect(body.data?.jobs.total).toBe(1);
158
+ expect(body.data?.jobs.paused).toBe(1);
159
+ expect(body.data?.jobs.active).toBe(0);
160
+ });
161
+
162
+ it("reports an unknown job", async () => {
163
+ const res = await fetch(`${base}/jobs/nope?action=pause`, {
164
+ method: "POST",
165
+ });
166
+
167
+ expect(res.status).toBe(404);
168
+ });
169
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/scheduler",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — scheduler",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/scheduler-adapter",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Scheduler Adapter — cron, systemd, launchd, internal adapters for autonomous runtime (RFC-0101 §6)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/session",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Session management system for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/session-api",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/session-export",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/shared-context",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — shared-context",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/skill-mcp-client",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "MCP client for Skills SaaS backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/skill-registry",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Skill Registry for pi-harness-runtime — dynamic skill discovery and invocation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/skills",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "File-based skill system with pi.dev compatibility and Hermes self-improvement",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/sprint-planner",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "description": "Sprint planning engine for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/subscription-engine",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/task-compiler",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Task Compiler for pi-harness-runtime — produces deterministic task DAG from CompiledRequirement",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/tencentdb-memory",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "TencentDB Agent Memory client - sync skills to centralized knowledge base",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tencentdb-sync",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Sync SKILL.md skills to TencentDB-Agent-Memory server (RFC-0105/0106)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/test-data-generator",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Test data generation for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/test-generator",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Auto-Test Generator — generate unit and E2E tests from source code (RFC-0013)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/todo-bd-sync",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Two-way sync between rpiv-todo overlay and bd issue tracker",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-estimation",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — token-estimation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-optimizer",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Token Budget Optimizer — maximize prompt value within token limits (RFC-0011)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tooling-preference",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — JS/TS tooling preference (Biome vs ESLint/Prettier) proposal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/trajectory",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "Trajectory capture for the write-review loop — persists cycle records for learning",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tui",
3
- "version": "1.1.429",
3
+ "version": "1.1.430",
4
4
  "description": "pi-harness-runtime — tui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",