@trevonistrevon/pi-loop 0.5.9 → 0.6.0

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 (121) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/AGENTS.md +52 -12
  3. package/CHANGELOG.md +59 -0
  4. package/CONTRIBUTING.md +13 -3
  5. package/README.md +47 -0
  6. package/coverage/coverage-final.json +35 -32
  7. package/coverage/index.html +64 -49
  8. package/coverage/src/api.ts.html +193 -0
  9. package/coverage/src/commands/index.html +32 -32
  10. package/coverage/src/commands/loop-command.ts.html +170 -170
  11. package/coverage/src/commands/tasks-command.ts.html +129 -129
  12. package/coverage/src/coordinator.ts.html +26 -26
  13. package/coverage/src/index.html +61 -106
  14. package/coverage/src/index.ts.html +224 -86
  15. package/coverage/src/loop-parse.ts.html +106 -73
  16. package/coverage/src/loop-reducer.ts.html +21 -21
  17. package/coverage/src/monitor-completion-coordinator.ts.html +1 -1
  18. package/coverage/src/monitor-manager.ts.html +95 -71
  19. package/coverage/src/monitor-reducer.ts.html +15 -15
  20. package/coverage/src/notification-reducer.ts.html +6 -6
  21. package/coverage/src/reducer-backed-store.ts.html +57 -57
  22. package/coverage/src/rpc/channels.ts.html +376 -0
  23. package/coverage/src/rpc/cross-extension-rpc.ts.html +541 -0
  24. package/coverage/src/rpc/index.html +131 -0
  25. package/coverage/src/runtime/index.html +68 -23
  26. package/coverage/src/{goal-coordinator.ts.html → runtime/loop-events.ts.html} +79 -73
  27. package/coverage/src/runtime/monitor-ondone-runtime.ts.html +5 -5
  28. package/coverage/src/runtime/native-task-rpc.ts.html +502 -0
  29. package/coverage/src/runtime/notification-runtime.ts.html +22 -22
  30. package/coverage/src/runtime/scope.ts.html +14 -14
  31. package/coverage/src/runtime/session-runtime.ts.html +32 -32
  32. package/coverage/src/runtime/task-backlog-runtime.ts.html +65 -23
  33. package/coverage/src/runtime/task-events.ts.html +2 -2
  34. package/coverage/src/runtime/task-mutations.ts.html +394 -0
  35. package/coverage/src/runtime/task-rpc.ts.html +102 -120
  36. package/coverage/src/scheduler.ts.html +29 -29
  37. package/coverage/src/store.ts.html +27 -27
  38. package/coverage/src/task-backlog-coordinator.ts.html +8 -8
  39. package/coverage/src/task-reducer.ts.html +42 -42
  40. package/coverage/src/task-store.ts.html +49 -49
  41. package/coverage/src/tools/index.html +39 -24
  42. package/coverage/src/tools/loop-tools.ts.html +12 -21
  43. package/coverage/src/tools/monitor-tools.ts.html +12 -21
  44. package/coverage/src/tools/native-task-tools.ts.html +57 -120
  45. package/coverage/src/tools/tool-result.ts.html +97 -0
  46. package/coverage/src/trigger-system.ts.html +12 -12
  47. package/coverage/src/ui/index.html +1 -1
  48. package/coverage/src/ui/widget.ts.html +23 -23
  49. package/dist/api.d.ts +11 -0
  50. package/dist/api.js +10 -0
  51. package/dist/commands/loop-command.js +2 -2
  52. package/dist/commands/tasks-command.js +3 -3
  53. package/dist/coordinator.d.ts +1 -1
  54. package/dist/index.js +48 -3
  55. package/dist/loop-parse.js +14 -5
  56. package/dist/reducer-backed-store.d.ts +1 -1
  57. package/dist/reducer-backed-store.js +1 -1
  58. package/dist/rpc/channels.d.ts +73 -0
  59. package/dist/rpc/channels.js +30 -0
  60. package/dist/rpc/cross-extension-rpc.d.ts +59 -0
  61. package/dist/rpc/cross-extension-rpc.js +117 -0
  62. package/dist/runtime/loop-events.d.ts +26 -0
  63. package/dist/runtime/loop-events.js +26 -0
  64. package/dist/runtime/native-task-rpc.d.ts +22 -0
  65. package/dist/runtime/native-task-rpc.js +50 -0
  66. package/dist/runtime/task-backlog-runtime.d.ts +3 -0
  67. package/dist/runtime/task-backlog-runtime.js +4 -1
  68. package/dist/runtime/task-mutations.d.ts +39 -0
  69. package/dist/runtime/task-mutations.js +68 -0
  70. package/dist/runtime/task-rpc.d.ts +4 -0
  71. package/dist/runtime/task-rpc.js +40 -58
  72. package/dist/scheduler.js +1 -1
  73. package/dist/tools/loop-tools.js +1 -3
  74. package/dist/tools/monitor-tools.js +1 -3
  75. package/dist/tools/native-task-tools.d.ts +2 -6
  76. package/dist/tools/native-task-tools.js +25 -55
  77. package/dist/tools/tool-result.d.ts +8 -0
  78. package/dist/tools/tool-result.js +4 -0
  79. package/docs/architecture/state-machine-migration.md +0 -44
  80. package/docs/architecture/state-machine-reducer-event-model.md +2 -3
  81. package/package.json +6 -1
  82. package/src/api.ts +36 -0
  83. package/src/commands/loop-command.ts +2 -2
  84. package/src/commands/tasks-command.ts +3 -3
  85. package/src/coordinator.ts +1 -1
  86. package/src/index.ts +49 -3
  87. package/src/loop-parse.ts +18 -7
  88. package/src/reducer-backed-store.ts +3 -3
  89. package/src/rpc/channels.ts +97 -0
  90. package/src/rpc/cross-extension-rpc.ts +152 -0
  91. package/src/runtime/loop-events.ts +60 -0
  92. package/src/runtime/native-task-rpc.ts +139 -0
  93. package/src/runtime/task-backlog-runtime.ts +14 -0
  94. package/src/runtime/task-mutations.ts +103 -0
  95. package/src/runtime/task-rpc.ts +48 -54
  96. package/src/scheduler.ts +1 -1
  97. package/src/tools/loop-tools.ts +1 -4
  98. package/src/tools/monitor-tools.ts +1 -4
  99. package/src/tools/native-task-tools.ts +31 -52
  100. package/src/tools/tool-result.ts +4 -0
  101. package/src/trigger-system.ts +4 -4
  102. package/vitest.config.ts +7 -7
  103. package/coverage/src/goal-reducer.ts.html +0 -982
  104. package/coverage/src/goal-store.ts.html +0 -742
  105. package/coverage/src/goal-verifier.ts.html +0 -808
  106. package/dist/goal-coordinator.d.ts +0 -22
  107. package/dist/goal-coordinator.js +0 -28
  108. package/dist/goal-reducer.d.ts +0 -98
  109. package/dist/goal-reducer.js +0 -190
  110. package/dist/goal-store.d.ts +0 -22
  111. package/dist/goal-store.js +0 -188
  112. package/dist/goal-types.d.ts +0 -82
  113. package/dist/goal-types.js +0 -1
  114. package/dist/goal-verifier.d.ts +0 -20
  115. package/dist/goal-verifier.js +0 -198
  116. package/docs/architecture/goal-state-schema.md +0 -505
  117. package/src/goal-coordinator.ts +0 -58
  118. package/src/goal-reducer.ts +0 -299
  119. package/src/goal-store.ts +0 -219
  120. package/src/goal-types.ts +0 -104
  121. package/src/goal-verifier.ts +0 -241
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
3
  import type { LoopEntry, MonitorEntry, Trigger } from "../types.js";
4
+ import { textResult } from "./tool-result.js";
4
5
 
5
6
  interface MonitorManagerLike {
6
7
  list(): MonitorEntry[];
@@ -26,10 +27,6 @@ export interface MonitorToolsOptions {
26
27
  handleMonitorDoneLoop: (doneLoop: LoopEntry, monitorId: string) => void;
27
28
  }
28
29
 
29
- function textResult(msg: string) {
30
- return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
31
- }
32
-
33
30
  function formatRemaining(ms: number): string {
34
31
  if (ms < 60000) return `${Math.round(ms / 1000)}s`;
35
32
  if (ms < 3600000) return `${Math.round(ms / 60000)}m`;
@@ -1,12 +1,17 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
- import { emitNativeTaskEvent } from "../runtime/task-events.js";
3
+ import {
4
+ createTask,
5
+ deleteTask,
6
+ type TaskBacklogResult,
7
+ type TaskMutationContext,
8
+ updateTask,
9
+ } from "../runtime/task-mutations.js";
4
10
  import { TaskStore } from "../task-store.js";
11
+ import type { TaskStatus } from "../task-types.js";
12
+ import { textResult } from "./tool-result.js";
5
13
 
6
- export interface TaskBacklogResult {
7
- created: boolean;
8
- entry?: { id: string };
9
- }
14
+ export type { TaskBacklogResult };
10
15
 
11
16
  export interface NativeTaskToolsOptions {
12
17
  pi: ExtensionAPI;
@@ -15,12 +20,15 @@ export interface NativeTaskToolsOptions {
15
20
  updateWidget: () => void;
16
21
  }
17
22
 
18
- function textResult(msg: string) {
19
- return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
23
+ function backlogSuffix(backlog: TaskBacklogResult): string {
24
+ return backlog.created && backlog.entry
25
+ ? `\nBacklog worker loop #${backlog.entry.id} created`
26
+ : "";
20
27
  }
21
28
 
22
29
  export function registerNativeTaskTools(options: NativeTaskToolsOptions): void {
23
30
  const { pi, taskStore, evaluateTaskBacklog, updateWidget } = options;
31
+ const mutationCtx: TaskMutationContext = { pi, taskStore, evaluateTaskBacklog, updateWidget };
24
32
 
25
33
  pi.registerTool({
26
34
  name: "TaskCreate",
@@ -47,15 +55,11 @@ Fields:
47
55
  description: Type.String({ description: "Detailed description of what needs to be done" }),
48
56
  }),
49
57
  async execute(_toolCallId, params) {
50
- const entry = taskStore.create(params.subject, params.description);
51
- emitNativeTaskEvent(pi, "tasks:created", entry);
52
- const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
53
- updateWidget();
54
-
55
- const autoLoopMsg = backlog.created && backlog.entry
56
- ? `\nBacklog worker loop #${backlog.entry.id} created`
57
- : "";
58
- return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
58
+ const { entry, backlog } = await createTask(mutationCtx, {
59
+ subject: params.subject,
60
+ description: params.description,
61
+ });
62
+ return textResult(`Task #${entry.id} created: ${entry.subject}${backlogSuffix(backlog)}`);
59
63
  },
60
64
  });
61
65
 
@@ -104,34 +108,15 @@ Parameters: id (required), status, subject, description`,
104
108
  }),
105
109
  async execute(_toolCallId, params) {
106
110
  const { id, status, subject, description } = params;
107
- let entry = taskStore.get(id);
108
- if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
109
-
110
- const previousStatus = entry.status;
111
- if (status === "in_progress") {
112
- entry = taskStore.start(id);
113
- if (entry) emitNativeTaskEvent(pi, "tasks:started", entry, previousStatus);
114
- } else if (status === "completed") {
115
- entry = taskStore.complete(id);
116
- if (entry) emitNativeTaskEvent(pi, "tasks:completed", entry, previousStatus);
117
- } else if (status === "pending") {
118
- entry = taskStore.reopen(id);
119
- if (entry) emitNativeTaskEvent(pi, "tasks:reopened", entry, previousStatus);
120
- }
121
-
122
- if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
123
- if (subject !== undefined || description !== undefined) {
124
- entry = taskStore.updateDetails(id, { subject, description });
125
- if (entry) emitNativeTaskEvent(pi, "tasks:updated", entry, previousStatus);
126
- }
127
- if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
128
- updateWidget();
129
- const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
111
+ const result = await updateTask(mutationCtx, {
112
+ id,
113
+ status: status as TaskStatus | undefined,
114
+ subject,
115
+ description,
116
+ });
117
+ if (!result) return textResult(`Task #${id} not found`);
130
118
  const statusMsg = status ? ` → ${status}` : "";
131
- const autoLoopMsg = backlog.created && backlog.entry
132
- ? `\nBacklog worker loop #${backlog.entry.id} created`
133
- : "";
134
- return Promise.resolve(textResult(`Task #${id} updated${statusMsg}${autoLoopMsg}`));
119
+ return textResult(`Task #${id} updated${statusMsg}${backlogSuffix(result.backlog)}`);
135
120
  },
136
121
  });
137
122
 
@@ -143,15 +128,9 @@ Parameters: id (required), status, subject, description`,
143
128
  id: Type.String({ description: "Task ID to delete" }),
144
129
  }),
145
130
  async execute(_toolCallId, params) {
146
- const existing = taskStore.get(params.id);
147
- const deleted = taskStore.delete(params.id);
148
- updateWidget();
149
- if (deleted) {
150
- if (existing) emitNativeTaskEvent(pi, "tasks:deleted", existing, existing.status);
151
- await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
152
- return Promise.resolve(textResult(`Task #${params.id} deleted`));
153
- }
154
- return Promise.resolve(textResult(`Task #${params.id} not found`));
131
+ const result = await deleteTask(mutationCtx, params.id);
132
+ if (!result) return textResult(`Task #${params.id} not found`);
133
+ return textResult(`Task #${params.id} deleted`);
155
134
  },
156
135
  });
157
136
  }
@@ -0,0 +1,4 @@
1
+ /** Plain-text tool result in the shape pi's registerTool expects. */
2
+ export function textResult(msg: string) {
3
+ return { content: [{ type: "text" as const, text: msg }], details: undefined };
4
+ }
@@ -58,7 +58,7 @@ export class TriggerSystem {
58
58
  }
59
59
  const subs = this.eventSubscriptions.get(source)!;
60
60
 
61
- const unsub = this.pi.events.on(source as any, (data: any) => {
61
+ const unsub = this.pi.events.on(source, (data: unknown) => {
62
62
  if (entry.trigger.type === "hybrid") {
63
63
  this.handleHybridFire(entry, data);
64
64
  } else {
@@ -71,7 +71,7 @@ export class TriggerSystem {
71
71
  subs.set(entry.id, unsub);
72
72
  }
73
73
 
74
- private handleHybridFire(entry: LoopEntry, _data: any): void {
74
+ private handleHybridFire(entry: LoopEntry, _data: unknown): void {
75
75
  const now = Date.now();
76
76
  const last = this.lastFireTime.get(entry.id) ?? 0;
77
77
  const debounceMs = entry.trigger.type === "hybrid" ? entry.trigger.debounceMs : 0;
@@ -123,7 +123,7 @@ export class TriggerSystem {
123
123
  }
124
124
  }
125
125
 
126
- private matchesFilter(data: any, filter?: string): boolean {
126
+ private matchesFilter(data: unknown, filter?: string): boolean {
127
127
  if (!filter) return true;
128
128
 
129
129
  if (filter.startsWith("regex:")) {
@@ -138,7 +138,7 @@ export class TriggerSystem {
138
138
  try {
139
139
  const parsed = JSON.parse(filter);
140
140
  for (const [key, value] of Object.entries(parsed)) {
141
- const dataValue = data?.[key];
141
+ const dataValue = (data as Record<string, unknown> | undefined)?.[key];
142
142
  if (dataValue === undefined) return false;
143
143
  if (typeof value === "object" && typeof dataValue === "object") {
144
144
  if (JSON.stringify(value) !== JSON.stringify(dataValue)) return false;
package/vitest.config.ts CHANGED
@@ -15,14 +15,14 @@ export default defineConfig({
15
15
  include: ["src/**/*.ts"],
16
16
  // Type-only modules and test helpers carry no executable logic worth gating.
17
17
  exclude: ["src/**/*-types.ts", "src/types.ts"],
18
- // Floors set just below current actuals (stmts 84%, branches 75%,
19
- // funcs 95%, lines 86%) to catch regressions. Raised in Phase 4 after the
20
- // runtime/ + tools/ suites landed.
18
+ // Floors set just below current actuals (stmts 91%, branches 83%,
19
+ // funcs 98%, lines 94%) to catch regressions. Re-anchored after the
20
+ // goal-subsystem removal and the rpc/native-task-rpc/command suites landed.
21
21
  thresholds: {
22
- statements: 82,
23
- branches: 73,
24
- functions: 94,
25
- lines: 84,
22
+ statements: 89,
23
+ branches: 80,
24
+ functions: 96,
25
+ lines: 91,
26
26
  },
27
27
  },
28
28
  },