pi-harness-runtime 1.1.350 → 1.1.351

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 (119) hide show
  1. package/README.md +3 -0
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/agent-policy/package.json +1 -1
  5. package/packages/architecture-generator/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-refactor/package.json +1 -1
  8. package/packages/autonomous-runtime/package.json +1 -1
  9. package/packages/cache-strategy/package.json +1 -1
  10. package/packages/capability-registry/package.json +1 -1
  11. package/packages/checkpoint/package.json +1 -1
  12. package/packages/cli-plugin-sdk/package.json +1 -1
  13. package/packages/clipboard/package.json +1 -1
  14. package/packages/clipboard-plugin/package.json +1 -1
  15. package/packages/code-generation/package.json +1 -1
  16. package/packages/code-review/package.json +1 -1
  17. package/packages/codex-adapter/package.json +1 -1
  18. package/packages/codex-watcher/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/connection-runtime/package.json +1 -1
  21. package/packages/context-compiler/package.json +1 -1
  22. package/packages/context-discovery/package.json +1 -1
  23. package/packages/context-manager/package.json +1 -1
  24. package/packages/contracts/dist/delegation-store.d.ts +35 -0
  25. package/packages/contracts/dist/delegation-store.js +172 -0
  26. package/packages/contracts/dist/index.d.ts +2 -0
  27. package/packages/contracts/dist/index.js +2 -0
  28. package/packages/contracts/dist/work-partitioner.d.ts +57 -0
  29. package/packages/contracts/dist/work-partitioner.js +165 -0
  30. package/packages/contracts/package.json +1 -1
  31. package/packages/contracts/src/delegation-store.ts +204 -0
  32. package/packages/contracts/src/index.ts +2 -0
  33. package/packages/contracts/src/work-partitioner.ts +251 -0
  34. package/packages/contracts/test/delegation.test.ts +350 -0
  35. package/packages/cookie-sanitizer/package.json +1 -1
  36. package/packages/cost-optimizer/package.json +1 -1
  37. package/packages/dependency-analyzer/package.json +1 -1
  38. package/packages/django-plugin/package.json +1 -1
  39. package/packages/doc-generator/package.json +1 -1
  40. package/packages/durable-review/package.json +1 -1
  41. package/packages/e2e-tester/package.json +1 -1
  42. package/packages/evaluation-engine/package.json +1 -1
  43. package/packages/evaluation-runner/package.json +1 -1
  44. package/packages/event-bus/package.json +1 -1
  45. package/packages/event-sink/package.json +1 -1
  46. package/packages/event-store/package.json +1 -1
  47. package/packages/experience-replay/package.json +1 -1
  48. package/packages/feedback-collector/package.json +1 -1
  49. package/packages/file-copy-helper/package.json +1 -1
  50. package/packages/framework-detector/package.json +1 -1
  51. package/packages/framework-plugin-sdk/package.json +1 -1
  52. package/packages/frappe-plugin/package.json +1 -1
  53. package/packages/generic-web-plugin/package.json +1 -1
  54. package/packages/health-monitor/package.json +1 -1
  55. package/packages/intent-analyzer/package.json +1 -1
  56. package/packages/jev-judge/package.json +1 -1
  57. package/packages/knowledge-graph/package.json +1 -1
  58. package/packages/knowledge-retrieval/package.json +1 -1
  59. package/packages/laravel-plugin/package.json +1 -1
  60. package/packages/learning-engine/package.json +1 -1
  61. package/packages/mcp-adapter/package.json +1 -1
  62. package/packages/memory-engine/package.json +1 -1
  63. package/packages/milestone-manager/package.json +1 -1
  64. package/packages/model-registry/package.json +1 -1
  65. package/packages/nextjs-plugin/package.json +1 -1
  66. package/packages/notification/package.json +1 -1
  67. package/packages/observability/package.json +1 -1
  68. package/packages/okf-indexer/package.json +1 -1
  69. package/packages/paperclip-integration/package.json +1 -1
  70. package/packages/performance-optimizer/package.json +1 -1
  71. package/packages/permission-mode/package.json +1 -1
  72. package/packages/privilege-broker/package.json +1 -1
  73. package/packages/project-analyzer/package.json +1 -1
  74. package/packages/project-bootstrap/package.json +1 -1
  75. package/packages/projection-engine/package.json +1 -1
  76. package/packages/prompt-compiler/package.json +1 -1
  77. package/packages/prompt-versioning/package.json +1 -1
  78. package/packages/provider-adapter-sdk/package.json +1 -1
  79. package/packages/provider-router/package.json +1 -1
  80. package/packages/provider-selector/package.json +1 -1
  81. package/packages/providers/package.json +1 -1
  82. package/packages/qdrant-skills/package.json +1 -1
  83. package/packages/quota-manager/package.json +1 -1
  84. package/packages/rate-limiter/package.json +1 -1
  85. package/packages/react-vite-plugin/package.json +1 -1
  86. package/packages/relay/package.json +1 -1
  87. package/packages/release-manager/package.json +1 -1
  88. package/packages/requirement-compiler/package.json +1 -1
  89. package/packages/runtime/package.json +1 -1
  90. package/packages/scheduler/package.json +1 -1
  91. package/packages/scheduler-adapter/package.json +1 -1
  92. package/packages/session/package.json +1 -1
  93. package/packages/session-api/package.json +1 -1
  94. package/packages/session-export/package.json +1 -1
  95. package/packages/shared-context/package.json +1 -1
  96. package/packages/skill-mcp-client/package.json +1 -1
  97. package/packages/skill-registry/package.json +1 -1
  98. package/packages/skills/package.json +1 -1
  99. package/packages/sprint-planner/package.json +1 -1
  100. package/packages/subscription-engine/package.json +1 -1
  101. package/packages/task-compiler/package.json +1 -1
  102. package/packages/telegram/package.json +1 -1
  103. package/packages/tencentdb-memory/package.json +1 -1
  104. package/packages/tencentdb-sync/package.json +1 -1
  105. package/packages/test-data-generator/package.json +1 -1
  106. package/packages/test-generator/package.json +1 -1
  107. package/packages/todo-bd-sync/package.json +1 -1
  108. package/packages/token-estimation/package.json +1 -1
  109. package/packages/token-optimizer/package.json +1 -1
  110. package/packages/trajectory/package.json +1 -1
  111. package/packages/tui/package.json +1 -1
  112. package/packages/types/package.json +1 -1
  113. package/packages/voice-api/package.json +1 -1
  114. package/packages/webmcp-tester/package.json +1 -1
  115. package/packages/wiki/package.json +1 -1
  116. package/packages/workflow-events/package.json +1 -1
  117. package/packages/workspace-scanner/package.json +1 -1
  118. package/packages/worktree/package.json +1 -1
  119. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Delegation Store
3
+ *
4
+ * Durable storage for delegation receipts.
5
+ * Stores in ignored harness state directory.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { join } from "path";
10
+ import type { DelegationReceipt } from "./delegation.js";
11
+
12
+ /**
13
+ * Store directory path
14
+ */
15
+ const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime", "delegation-state");
16
+
17
+ /**
18
+ * Ensure store directory exists
19
+ */
20
+ function ensureStoreDir(): void {
21
+ if (!existsSync(STORE_DIR)) {
22
+ mkdirSync(STORE_DIR, { recursive: true });
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get receipt file path for workspace + task
28
+ */
29
+ function getReceiptPath(workspacePath: string, taskId: string): string {
30
+ // Sanitize path for use as filename
31
+ const safePath = workspacePath.replace(/[^a-zA-Z0-9_-]/g, "_").slice(-100);
32
+ const safeTask = taskId.replace(/[^a-zA-Z0-9_-]/g, "_");
33
+ return join(STORE_DIR, `${safePath}__${safeTask}.json`);
34
+ }
35
+
36
+ /**
37
+ * Save delegation receipt
38
+ */
39
+ export function saveDelegationReceipt(receipt: DelegationReceipt): void {
40
+ ensureStoreDir();
41
+ const path = getReceiptPath(receipt.workspacePath, receipt.parentTaskId);
42
+
43
+ // Atomic write: write to temp, then rename
44
+ const tempPath = path + ".tmp";
45
+ writeFileSync(tempPath, JSON.stringify(receipt, null, 2), "utf8");
46
+
47
+ // Rename is atomic on POSIX
48
+ try {
49
+ const { renameSync } = require("fs");
50
+ renameSync(tempPath, path);
51
+ } catch {
52
+ // Fallback: just move the file
53
+ try {
54
+ writeFileSync(path, JSON.stringify(receipt, null, 2), "utf8");
55
+ } catch {
56
+ // Ignore write errors
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Load delegation receipt
63
+ */
64
+ export function loadDelegationReceipt(
65
+ workspacePath: string,
66
+ taskId: string
67
+ ): DelegationReceipt | null {
68
+ const path = getReceiptPath(workspacePath, taskId);
69
+
70
+ if (!existsSync(path)) {
71
+ return null;
72
+ }
73
+
74
+ try {
75
+ const content = readFileSync(path, "utf8");
76
+ const receipt = JSON.parse(content) as DelegationReceipt;
77
+
78
+ // Validate version
79
+ if (receipt.version !== 1) {
80
+ // Future migration logic would go here
81
+ return null;
82
+ }
83
+
84
+ return receipt;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Delete delegation receipt
92
+ */
93
+ export function deleteDelegationReceipt(
94
+ workspacePath: string,
95
+ taskId: string
96
+ ): void {
97
+ const path = getReceiptPath(workspacePath, taskId);
98
+
99
+ if (existsSync(path)) {
100
+ try {
101
+ const { unlinkSync } = require("fs");
102
+ unlinkSync(path);
103
+ } catch {
104
+ // Ignore delete errors
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * List all receipts for a workspace
111
+ */
112
+ export function listDelegationReceipts(workspacePath: string): DelegationReceipt[] {
113
+ ensureStoreDir();
114
+ const receipts: DelegationReceipt[] = [];
115
+
116
+ try {
117
+ const { readdirSync } = require("fs");
118
+ const files = readdirSync(STORE_DIR);
119
+
120
+ const safePathPrefix = workspacePath.replace(/[^a-zA-Z0-9_-]/g, "_").slice(-100) + "__";
121
+
122
+ for (const file of files) {
123
+ if (!file.startsWith(safePathPrefix) || !file.endsWith(".json")) {
124
+ continue;
125
+ }
126
+
127
+ try {
128
+ const content = readFileSync(join(STORE_DIR, file), "utf8");
129
+ const receipt = JSON.parse(content) as DelegationReceipt;
130
+ if (receipt.workspacePath === workspacePath) {
131
+ receipts.push(receipt);
132
+ }
133
+ } catch {
134
+ // Skip invalid files
135
+ }
136
+ }
137
+ } catch {
138
+ // Directory doesn't exist or not readable
139
+ }
140
+
141
+ return receipts;
142
+ }
143
+
144
+ /**
145
+ * Update receipt status
146
+ */
147
+ export function updateReceiptStatus(
148
+ workspacePath: string,
149
+ taskId: string,
150
+ status: DelegationReceipt["status"],
151
+ updates?: Partial<DelegationReceipt>
152
+ ): DelegationReceipt | null {
153
+ const receipt = loadDelegationReceipt(workspacePath, taskId);
154
+
155
+ if (!receipt) {
156
+ return null;
157
+ }
158
+
159
+ const updated: DelegationReceipt = {
160
+ ...receipt,
161
+ ...updates,
162
+ status,
163
+ updatedAt: Date.now(),
164
+ };
165
+
166
+ saveDelegationReceipt(updated);
167
+ return updated;
168
+ }
169
+
170
+ /**
171
+ * Check if receipt exists for task
172
+ */
173
+ export function hasReceipt(workspacePath: string, taskId: string): boolean {
174
+ const path = getReceiptPath(workspacePath, taskId);
175
+ return existsSync(path);
176
+ }
177
+
178
+ /**
179
+ * Compact receipt for storage after compaction
180
+ */
181
+ export function compactReceipt(receipt: DelegationReceipt): DelegationReceipt {
182
+ return {
183
+ version: receipt.version,
184
+ workspacePath: receipt.workspacePath,
185
+ sessionId: receipt.sessionId,
186
+ parentTaskId: receipt.parentTaskId,
187
+ scopeFingerprint: receipt.scopeFingerprint,
188
+ decision: {
189
+ strategy: receipt.decision.strategy,
190
+ reason: receipt.decision.reason,
191
+ workGroups: receipt.decision.workGroups.filter((g) => g.status === "pending" || g.status === "dispatched" || g.status === "running"),
192
+ validationExpectations: receipt.decision.validationExpectations,
193
+ confidence: receipt.decision.confidence,
194
+ jevUsed: receipt.decision.jevUsed,
195
+ reassessmentTriggers: receipt.decision.reassessmentTriggers,
196
+ },
197
+ createdAt: receipt.createdAt,
198
+ updatedAt: Date.now(),
199
+ status: receipt.status,
200
+ childIds: receipt.childIds,
201
+ pendingValidation: receipt.pendingValidation,
202
+ userConstraints: receipt.userConstraints,
203
+ };
204
+ }
@@ -21,4 +21,6 @@ export * from './notifications.js';
21
21
  export * from './approvals.js';
22
22
  export * from './delegation.js';
23
23
  export * from './delegation-assessor.js';
24
+ export * from './delegation-store.js';
24
25
  export * from './scope-discovery.js';
26
+ export * from './work-partitioner.js';
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Work Partitioner
3
+ *
4
+ * Partitions work into disjoint file ownership groups.
5
+ */
6
+
7
+ import type { WorkGroup, ScopeFacts } from "./delegation.js";
8
+
9
+ /**
10
+ * File dependency info
11
+ */
12
+ export interface FileDependency {
13
+ path: string;
14
+ imports: string[];
15
+ exports: string[];
16
+ }
17
+
18
+ /**
19
+ * Partition configuration
20
+ */
21
+ export interface PartitionConfig {
22
+ /** Maximum files per group */
23
+ maxFilesPerGroup: number;
24
+ /** Whether to group by directory */
25
+ groupByDirectory: boolean;
26
+ /** Shared files that shouldn't be partitioned */
27
+ sharedPaths: string[];
28
+ }
29
+
30
+ /**
31
+ * Default partition config
32
+ */
33
+ export const DEFAULT_PARTITION_CONFIG: PartitionConfig = {
34
+ maxFilesPerGroup: 15,
35
+ groupByDirectory: true,
36
+ sharedPaths: [
37
+ "index.ts",
38
+ "index.js",
39
+ "main.ts",
40
+ "main.js",
41
+ "package.json",
42
+ "tsconfig.json",
43
+ ],
44
+ };
45
+
46
+ /**
47
+ * Partition files into ownership groups
48
+ */
49
+ export function partitionWork(
50
+ files: string[],
51
+ facts: ScopeFacts,
52
+ config: Partial<PartitionConfig> = {}
53
+ ): WorkGroup[] {
54
+ const cfg = { ...DEFAULT_PARTITION_CONFIG, ...config };
55
+
56
+ // Filter out shared paths
57
+ const ownedFiles = files.filter(
58
+ (f) => !cfg.sharedPaths.some((s) => f.endsWith(s))
59
+ );
60
+
61
+ // Group files by directory
62
+ const dirGroups = groupByDirectory(ownedFiles);
63
+
64
+ // Further partition large groups
65
+ const groups: WorkGroup[] = [];
66
+ let groupId = 1;
67
+
68
+ for (const [dir, dirFiles] of Object.entries(dirGroups)) {
69
+ if (dirFiles.length <= cfg.maxFilesPerGroup) {
70
+ groups.push(createGroup(groupId++, dir, dirFiles, facts.isMechanical));
71
+ } else {
72
+ // Split large directories
73
+ const subGroups = splitGroup(dirFiles, cfg.maxFilesPerGroup);
74
+ for (const subFiles of subGroups) {
75
+ groups.push(createGroup(groupId++, dir, subFiles, facts.isMechanical));
76
+ }
77
+ }
78
+ }
79
+
80
+ return groups;
81
+ }
82
+
83
+ /**
84
+ * Group files by directory
85
+ */
86
+ function groupByDirectory(files: string[]): Record<string, string[]> {
87
+ const groups: Record<string, string[]> = {};
88
+
89
+ for (const file of files) {
90
+ // Get directory part of path
91
+ const parts = file.split("/");
92
+ const dir = parts.length > 1 ? parts.slice(0, -1).join("/") : ".";
93
+
94
+ if (!groups[dir]) {
95
+ groups[dir] = [];
96
+ }
97
+ groups[dir].push(file);
98
+ }
99
+
100
+ return groups;
101
+ }
102
+
103
+ /**
104
+ * Create a work group
105
+ */
106
+ function createGroup(
107
+ id: number,
108
+ description: string,
109
+ files: string[],
110
+ isMechanical: boolean
111
+ ): WorkGroup {
112
+ return {
113
+ groupId: `group-${id}`,
114
+ description: `${description} (${files.length} files)`,
115
+ ownedPaths: files,
116
+ isMechanical,
117
+ status: "pending",
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Split a group into smaller subgroups
123
+ */
124
+ function splitGroup(files: string[], maxSize: number): string[][] {
125
+ const subgroups: string[][] = [];
126
+
127
+ for (let i = 0; i < files.length; i += maxSize) {
128
+ subgroups.push(files.slice(i, i + maxSize));
129
+ }
130
+
131
+ return subgroups;
132
+ }
133
+
134
+ /**
135
+ * Detect ownership conflicts
136
+ */
137
+ export function detectOwnershipConflicts(
138
+ groups: WorkGroup[],
139
+ newPaths: string[]
140
+ ): string[] {
141
+ const conflicts: string[] = [];
142
+
143
+ for (const path of newPaths) {
144
+ for (const group of groups) {
145
+ if (
146
+ group.status !== "cancelled" &&
147
+ group.status !== "failed" &&
148
+ group.ownedPaths.includes(path)
149
+ ) {
150
+ conflicts.push(`${path} already owned by ${group.groupId}`);
151
+ }
152
+ }
153
+ }
154
+
155
+ return conflicts;
156
+ }
157
+
158
+ /**
159
+ * Assign work to group
160
+ */
161
+ export function assignWorkToGroup(
162
+ groups: WorkGroup[],
163
+ groupId: string,
164
+ paths: string[]
165
+ ): WorkGroup | null {
166
+ const group = groups.find((g) => g.groupId === groupId);
167
+
168
+ if (!group) {
169
+ return null;
170
+ }
171
+
172
+ // Check for conflicts
173
+ const conflicts = detectOwnershipConflicts(
174
+ groups.filter((g) => g.groupId !== groupId),
175
+ paths
176
+ );
177
+
178
+ if (conflicts.length > 0) {
179
+ // Return group with conflict info
180
+ return {
181
+ ...group,
182
+ status: "failed",
183
+ };
184
+ }
185
+
186
+ return {
187
+ ...group,
188
+ ownedPaths: [...new Set([...group.ownedPaths, ...paths])],
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Get unowned files
194
+ */
195
+ export function getUnownedFiles(
196
+ allFiles: string[],
197
+ groups: WorkGroup[]
198
+ ): string[] {
199
+ const ownedFiles = new Set<string>();
200
+
201
+ for (const group of groups) {
202
+ if (group.status !== "cancelled" && group.status !== "failed") {
203
+ for (const path of group.ownedPaths) {
204
+ ownedFiles.add(path);
205
+ }
206
+ }
207
+ }
208
+
209
+ return allFiles.filter((f) => !ownedFiles.has(f));
210
+ }
211
+
212
+ /**
213
+ * Update group status
214
+ */
215
+ export function updateGroupStatus(
216
+ groups: WorkGroup[],
217
+ groupId: string,
218
+ status: WorkGroup["status"],
219
+ childId?: string
220
+ ): WorkGroup[] {
221
+ return groups.map((g) => {
222
+ if (g.groupId !== groupId) {
223
+ return g;
224
+ }
225
+
226
+ return {
227
+ ...g,
228
+ status,
229
+ childId: childId || g.childId,
230
+ };
231
+ });
232
+ }
233
+
234
+ /**
235
+ * Check if all groups are complete
236
+ */
237
+ export function areAllGroupsComplete(groups: WorkGroup[]): boolean {
238
+ return groups.every(
239
+ (g) =>
240
+ g.status === "completed" ||
241
+ g.status === "failed" ||
242
+ g.status === "cancelled"
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Get pending groups
248
+ */
249
+ export function getPendingGroups(groups: WorkGroup[]): WorkGroup[] {
250
+ return groups.filter((g) => g.status === "pending");
251
+ }