pi-harness-runtime 1.1.349 → 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.
- package/README.md +3 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/delegation-assessor.d.ts +27 -0
- package/packages/contracts/dist/delegation-assessor.js +210 -0
- package/packages/contracts/dist/delegation-store.d.ts +35 -0
- package/packages/contracts/dist/delegation-store.js +172 -0
- package/packages/contracts/dist/delegation.d.ts +142 -0
- package/packages/contracts/dist/delegation.js +33 -0
- package/packages/contracts/dist/index.d.ts +5 -0
- package/packages/contracts/dist/index.js +5 -0
- package/packages/contracts/dist/scope-discovery.d.ts +65 -0
- package/packages/contracts/dist/scope-discovery.js +247 -0
- package/packages/contracts/dist/work-partitioner.d.ts +57 -0
- package/packages/contracts/dist/work-partitioner.js +165 -0
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/delegation-assessor.ts +268 -0
- package/packages/contracts/src/delegation-store.ts +204 -0
- package/packages/contracts/src/delegation.ts +174 -0
- package/packages/contracts/src/index.ts +5 -0
- package/packages/contracts/src/scope-discovery.ts +352 -0
- package/packages/contracts/src/work-partitioner.ts +251 -0
- package/packages/contracts/test/delegation.test.ts +350 -0
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,10 +30,13 @@ pi install npm:pi-harness-runtime
|
|
|
30
30
|
This runtime works best with these extensions (auto-installed by pi-harness-runtime):
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
+
pi install npm:pi-subagents # Sub-agent orchestration for large refactors
|
|
33
34
|
pi install npm:pi-mcp-adapter # MCP protocol adapter for external tools
|
|
34
35
|
pi install npm:@juicesharp/rpiv-todo # Task tracking with bd sync
|
|
35
36
|
```
|
|
36
37
|
|
|
38
|
+
The `pi-subagents` extension enables delegation assessment and parallel task execution for large refactoring work (10+ files, 50+ call sites).
|
|
39
|
+
|
|
37
40
|
Requires Pi v0.37.3+.
|
|
38
41
|
|
|
39
42
|
### Headless Server Setup (herdr clipboard bridge)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.351",
|
|
4
4
|
"description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Assessor
|
|
3
|
+
*
|
|
4
|
+
* Assesses whether work should be delegated to sub-agents based on scope.
|
|
5
|
+
*/
|
|
6
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { ScopeFacts, DelegationDecision, DelegationTriggerConfig, ExtensionCapability } from "./delegation.js";
|
|
8
|
+
/**
|
|
9
|
+
* Discover extension capabilities for delegation
|
|
10
|
+
*/
|
|
11
|
+
export declare function discoverDelegationCapability(pi: ExtensionAPI): Promise<ExtensionCapability | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Estimate scope from file analysis
|
|
14
|
+
*/
|
|
15
|
+
export declare function estimateScope(_pi: ExtensionAPI, patterns: string[]): Promise<ScopeFacts>;
|
|
16
|
+
/**
|
|
17
|
+
* Make deterministic assessment without Jev
|
|
18
|
+
*/
|
|
19
|
+
export declare function makeDeterministicAssessment(facts: ScopeFacts, capability: ExtensionCapability | null): DelegationDecision;
|
|
20
|
+
/**
|
|
21
|
+
* Assess delegation need
|
|
22
|
+
*/
|
|
23
|
+
export declare function assessDelegation(pi: ExtensionAPI, facts: ScopeFacts, config?: DelegationTriggerConfig): Promise<DelegationDecision | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Validate assessment is still valid
|
|
26
|
+
*/
|
|
27
|
+
export declare function isAssessmentStale(decision: DelegationDecision, currentFacts: ScopeFacts, originalFacts: ScopeFacts): boolean;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Assessor
|
|
3
|
+
*
|
|
4
|
+
* Assesses whether work should be delegated to sub-agents based on scope.
|
|
5
|
+
*/
|
|
6
|
+
import { shouldTriggerAssessment } from "./delegation.js";
|
|
7
|
+
/**
|
|
8
|
+
* Sub-agent extension names to check for
|
|
9
|
+
*/
|
|
10
|
+
const SUBAGENT_EXTENSIONS = [
|
|
11
|
+
"pi-subagents",
|
|
12
|
+
"@earendil-works/pi-subagents",
|
|
13
|
+
"subagent",
|
|
14
|
+
"spawn",
|
|
15
|
+
"delegate",
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Tool names that indicate sub-agent capability
|
|
19
|
+
*/
|
|
20
|
+
const SUBAGENT_TOOLS = [
|
|
21
|
+
"subagent",
|
|
22
|
+
"spawn",
|
|
23
|
+
"delegate",
|
|
24
|
+
"run_subagent",
|
|
25
|
+
"run_agent",
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Discover extension capabilities for delegation
|
|
29
|
+
*/
|
|
30
|
+
export async function discoverDelegationCapability(pi) {
|
|
31
|
+
// Get all available tools
|
|
32
|
+
const allTools = pi.getAllTools();
|
|
33
|
+
const toolNames = allTools.map((t) => t.name);
|
|
34
|
+
const activeTools = pi.getActiveTools();
|
|
35
|
+
// Check for sub-agent tools
|
|
36
|
+
const hasSubagentTool = SUBAGENT_TOOLS.some((tool) => toolNames.includes(tool) || activeTools.includes(tool));
|
|
37
|
+
// Check for subagent extension registration
|
|
38
|
+
// The extension might register tools under different names
|
|
39
|
+
const possibleCapabilities = [];
|
|
40
|
+
// Verify by attempting a capability check
|
|
41
|
+
// If subagent tools exist, assume capability
|
|
42
|
+
if (hasSubagentTool) {
|
|
43
|
+
return {
|
|
44
|
+
name: "subagent-extension",
|
|
45
|
+
supportsSubagents: true,
|
|
46
|
+
supportsWorkIsolation: true, // Assume based on tool presence
|
|
47
|
+
supportsCancellation: true,
|
|
48
|
+
availableTools: activeTools.filter((t) => SUBAGENT_TOOLS.some((st) => t.includes(st))),
|
|
49
|
+
verified: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Check if pi-subagents skill is mentioned in available tools
|
|
53
|
+
const subagentMentioned = toolNames.some((t) => t.includes("subagent") || t.includes("spawn"));
|
|
54
|
+
if (subagentMentioned) {
|
|
55
|
+
return {
|
|
56
|
+
name: "pi-subagents",
|
|
57
|
+
supportsSubagents: true,
|
|
58
|
+
supportsWorkIsolation: true,
|
|
59
|
+
supportsCancellation: true,
|
|
60
|
+
availableTools: toolNames.filter((t) => t.includes("subagent")),
|
|
61
|
+
verified: false, // Not verified - just mentioned
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Estimate scope from file analysis
|
|
68
|
+
*/
|
|
69
|
+
export async function estimateScope(_pi, patterns) {
|
|
70
|
+
// TODO: Implement actual file discovery
|
|
71
|
+
// For now, return placeholder
|
|
72
|
+
return {
|
|
73
|
+
estimatedFiles: patterns.length,
|
|
74
|
+
observedFiles: 0,
|
|
75
|
+
estimatedCallSites: 0,
|
|
76
|
+
observedCallSites: 0,
|
|
77
|
+
independentComponents: 1,
|
|
78
|
+
tightlyCoupled: false,
|
|
79
|
+
isMechanical: true,
|
|
80
|
+
isComplex: false,
|
|
81
|
+
fileTypes: {},
|
|
82
|
+
fingerprint: "",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Make deterministic assessment without Jev
|
|
87
|
+
*/
|
|
88
|
+
export function makeDeterministicAssessment(facts, capability) {
|
|
89
|
+
// No capability - must use single agent
|
|
90
|
+
if (!capability?.verified) {
|
|
91
|
+
return {
|
|
92
|
+
strategy: "unavailable",
|
|
93
|
+
reason: "No verified sub-agent capability available",
|
|
94
|
+
workGroups: [],
|
|
95
|
+
validationExpectations: "Parent handles all validation",
|
|
96
|
+
confidence: "high",
|
|
97
|
+
jevUsed: false,
|
|
98
|
+
reassessmentTriggers: ["Extension becomes available"],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Small scope - single agent
|
|
102
|
+
if (facts.observedFiles < 5 && facts.observedCallSites < 20) {
|
|
103
|
+
return {
|
|
104
|
+
strategy: "single_agent",
|
|
105
|
+
reason: "Small scope - delegation overhead not justified",
|
|
106
|
+
workGroups: [],
|
|
107
|
+
validationExpectations: "Standard parent validation",
|
|
108
|
+
confidence: "high",
|
|
109
|
+
jevUsed: false,
|
|
110
|
+
reassessmentTriggers: [],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Mechanical transformation with reliable script
|
|
114
|
+
if (facts.isMechanical && !facts.isComplex) {
|
|
115
|
+
return {
|
|
116
|
+
strategy: "script_only",
|
|
117
|
+
reason: "Mechanical transformation - script approach is reliable",
|
|
118
|
+
workGroups: [],
|
|
119
|
+
validationExpectations: "Script output validation + test run",
|
|
120
|
+
confidence: "high",
|
|
121
|
+
jevUsed: false,
|
|
122
|
+
reassessmentTriggers: ["Transformation proves unreliable"],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Multiple independent components
|
|
126
|
+
if (facts.independentComponents >= 2 && !facts.tightlyCoupled) {
|
|
127
|
+
const groups = partitionIntoGroups(facts);
|
|
128
|
+
return {
|
|
129
|
+
strategy: "delegate",
|
|
130
|
+
reason: `${facts.independentComponents} independent components - delegation recommended`,
|
|
131
|
+
workGroups: groups,
|
|
132
|
+
validationExpectations: "Each group validated independently, then integrated",
|
|
133
|
+
confidence: "medium",
|
|
134
|
+
jevUsed: false,
|
|
135
|
+
reassessmentTriggers: ["Scope expands significantly", "Component coupling discovered"],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Tightly coupled - sequential or single agent
|
|
139
|
+
if (facts.tightlyCoupled) {
|
|
140
|
+
return {
|
|
141
|
+
strategy: "script_then_delegate",
|
|
142
|
+
reason: "Coupled components - script shared parts, delegate isolated changes",
|
|
143
|
+
workGroups: [],
|
|
144
|
+
validationExpectations: "Script validates shared API, delegate validates components",
|
|
145
|
+
confidence: "medium",
|
|
146
|
+
jevUsed: false,
|
|
147
|
+
reassessmentTriggers: ["Coupling reduced after refactoring"],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// Default - single agent with monitoring
|
|
151
|
+
return {
|
|
152
|
+
strategy: "single_agent",
|
|
153
|
+
reason: "Default to single agent for bounded scope",
|
|
154
|
+
workGroups: [],
|
|
155
|
+
validationExpectations: "Parent validates all changes",
|
|
156
|
+
confidence: "low",
|
|
157
|
+
jevUsed: false,
|
|
158
|
+
reassessmentTriggers: ["Scope exceeds threshold"],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Partition work into groups
|
|
163
|
+
*/
|
|
164
|
+
function partitionIntoGroups(facts) {
|
|
165
|
+
const groups = [];
|
|
166
|
+
const filesPerGroup = Math.ceil(facts.observedFiles / facts.independentComponents);
|
|
167
|
+
for (let i = 0; i < facts.independentComponents; i++) {
|
|
168
|
+
groups.push({
|
|
169
|
+
groupId: `group-${i + 1}`,
|
|
170
|
+
description: `Component ${i + 1} of ${facts.independentComponents}`,
|
|
171
|
+
ownedPaths: [], // TODO: Populate with actual files
|
|
172
|
+
isMechanical: facts.isMechanical,
|
|
173
|
+
status: "pending",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return groups;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Assess delegation need
|
|
180
|
+
*/
|
|
181
|
+
export async function assessDelegation(pi, facts, config = { minFiles: 10, minCallSites: 50, minIndependentComponents: 2, assessOnDiscovery: true }) {
|
|
182
|
+
// Check if assessment should trigger
|
|
183
|
+
if (!shouldTriggerAssessment(facts, config)) {
|
|
184
|
+
return null; // No assessment needed
|
|
185
|
+
}
|
|
186
|
+
// Discover capability
|
|
187
|
+
const capability = await discoverDelegationCapability(pi);
|
|
188
|
+
// Make deterministic assessment
|
|
189
|
+
return makeDeterministicAssessment(facts, capability);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Validate assessment is still valid
|
|
193
|
+
*/
|
|
194
|
+
export function isAssessmentStale(decision, currentFacts, originalFacts) {
|
|
195
|
+
// Scope significantly expanded
|
|
196
|
+
if (currentFacts.observedFiles > originalFacts.observedFiles * 1.5) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
// New coupling discovered
|
|
200
|
+
if (currentFacts.tightlyCoupled && !originalFacts.tightlyCoupled) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
// Reassessment triggers
|
|
204
|
+
return decision.reassessmentTriggers.some((trigger) => {
|
|
205
|
+
if (trigger.includes("scope expands")) {
|
|
206
|
+
return currentFacts.observedFiles > originalFacts.observedFiles * 1.5;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Store
|
|
3
|
+
*
|
|
4
|
+
* Durable storage for delegation receipts.
|
|
5
|
+
* Stores in ignored harness state directory.
|
|
6
|
+
*/
|
|
7
|
+
import type { DelegationReceipt } from "./delegation.js";
|
|
8
|
+
/**
|
|
9
|
+
* Save delegation receipt
|
|
10
|
+
*/
|
|
11
|
+
export declare function saveDelegationReceipt(receipt: DelegationReceipt): void;
|
|
12
|
+
/**
|
|
13
|
+
* Load delegation receipt
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadDelegationReceipt(workspacePath: string, taskId: string): DelegationReceipt | null;
|
|
16
|
+
/**
|
|
17
|
+
* Delete delegation receipt
|
|
18
|
+
*/
|
|
19
|
+
export declare function deleteDelegationReceipt(workspacePath: string, taskId: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* List all receipts for a workspace
|
|
22
|
+
*/
|
|
23
|
+
export declare function listDelegationReceipts(workspacePath: string): DelegationReceipt[];
|
|
24
|
+
/**
|
|
25
|
+
* Update receipt status
|
|
26
|
+
*/
|
|
27
|
+
export declare function updateReceiptStatus(workspacePath: string, taskId: string, status: DelegationReceipt["status"], updates?: Partial<DelegationReceipt>): DelegationReceipt | null;
|
|
28
|
+
/**
|
|
29
|
+
* Check if receipt exists for task
|
|
30
|
+
*/
|
|
31
|
+
export declare function hasReceipt(workspacePath: string, taskId: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Compact receipt for storage after compaction
|
|
34
|
+
*/
|
|
35
|
+
export declare function compactReceipt(receipt: DelegationReceipt): DelegationReceipt;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Store
|
|
3
|
+
*
|
|
4
|
+
* Durable storage for delegation receipts.
|
|
5
|
+
* Stores in ignored harness state directory.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
/**
|
|
10
|
+
* Store directory path
|
|
11
|
+
*/
|
|
12
|
+
const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime", "delegation-state");
|
|
13
|
+
/**
|
|
14
|
+
* Ensure store directory exists
|
|
15
|
+
*/
|
|
16
|
+
function ensureStoreDir() {
|
|
17
|
+
if (!existsSync(STORE_DIR)) {
|
|
18
|
+
mkdirSync(STORE_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get receipt file path for workspace + task
|
|
23
|
+
*/
|
|
24
|
+
function getReceiptPath(workspacePath, taskId) {
|
|
25
|
+
// Sanitize path for use as filename
|
|
26
|
+
const safePath = workspacePath.replace(/[^a-zA-Z0-9_-]/g, "_").slice(-100);
|
|
27
|
+
const safeTask = taskId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
28
|
+
return join(STORE_DIR, `${safePath}__${safeTask}.json`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Save delegation receipt
|
|
32
|
+
*/
|
|
33
|
+
export function saveDelegationReceipt(receipt) {
|
|
34
|
+
ensureStoreDir();
|
|
35
|
+
const path = getReceiptPath(receipt.workspacePath, receipt.parentTaskId);
|
|
36
|
+
// Atomic write: write to temp, then rename
|
|
37
|
+
const tempPath = path + ".tmp";
|
|
38
|
+
writeFileSync(tempPath, JSON.stringify(receipt, null, 2), "utf8");
|
|
39
|
+
// Rename is atomic on POSIX
|
|
40
|
+
try {
|
|
41
|
+
const { renameSync } = require("fs");
|
|
42
|
+
renameSync(tempPath, path);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Fallback: just move the file
|
|
46
|
+
try {
|
|
47
|
+
writeFileSync(path, JSON.stringify(receipt, null, 2), "utf8");
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignore write errors
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Load delegation receipt
|
|
56
|
+
*/
|
|
57
|
+
export function loadDelegationReceipt(workspacePath, taskId) {
|
|
58
|
+
const path = getReceiptPath(workspacePath, taskId);
|
|
59
|
+
if (!existsSync(path)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const content = readFileSync(path, "utf8");
|
|
64
|
+
const receipt = JSON.parse(content);
|
|
65
|
+
// Validate version
|
|
66
|
+
if (receipt.version !== 1) {
|
|
67
|
+
// Future migration logic would go here
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return receipt;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Delete delegation receipt
|
|
78
|
+
*/
|
|
79
|
+
export function deleteDelegationReceipt(workspacePath, taskId) {
|
|
80
|
+
const path = getReceiptPath(workspacePath, taskId);
|
|
81
|
+
if (existsSync(path)) {
|
|
82
|
+
try {
|
|
83
|
+
const { unlinkSync } = require("fs");
|
|
84
|
+
unlinkSync(path);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Ignore delete errors
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List all receipts for a workspace
|
|
93
|
+
*/
|
|
94
|
+
export function listDelegationReceipts(workspacePath) {
|
|
95
|
+
ensureStoreDir();
|
|
96
|
+
const receipts = [];
|
|
97
|
+
try {
|
|
98
|
+
const { readdirSync } = require("fs");
|
|
99
|
+
const files = readdirSync(STORE_DIR);
|
|
100
|
+
const safePathPrefix = workspacePath.replace(/[^a-zA-Z0-9_-]/g, "_").slice(-100) + "__";
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
if (!file.startsWith(safePathPrefix) || !file.endsWith(".json")) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const content = readFileSync(join(STORE_DIR, file), "utf8");
|
|
107
|
+
const receipt = JSON.parse(content);
|
|
108
|
+
if (receipt.workspacePath === workspacePath) {
|
|
109
|
+
receipts.push(receipt);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Skip invalid files
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Directory doesn't exist or not readable
|
|
119
|
+
}
|
|
120
|
+
return receipts;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Update receipt status
|
|
124
|
+
*/
|
|
125
|
+
export function updateReceiptStatus(workspacePath, taskId, status, updates) {
|
|
126
|
+
const receipt = loadDelegationReceipt(workspacePath, taskId);
|
|
127
|
+
if (!receipt) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const updated = {
|
|
131
|
+
...receipt,
|
|
132
|
+
...updates,
|
|
133
|
+
status,
|
|
134
|
+
updatedAt: Date.now(),
|
|
135
|
+
};
|
|
136
|
+
saveDelegationReceipt(updated);
|
|
137
|
+
return updated;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if receipt exists for task
|
|
141
|
+
*/
|
|
142
|
+
export function hasReceipt(workspacePath, taskId) {
|
|
143
|
+
const path = getReceiptPath(workspacePath, taskId);
|
|
144
|
+
return existsSync(path);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Compact receipt for storage after compaction
|
|
148
|
+
*/
|
|
149
|
+
export function compactReceipt(receipt) {
|
|
150
|
+
return {
|
|
151
|
+
version: receipt.version,
|
|
152
|
+
workspacePath: receipt.workspacePath,
|
|
153
|
+
sessionId: receipt.sessionId,
|
|
154
|
+
parentTaskId: receipt.parentTaskId,
|
|
155
|
+
scopeFingerprint: receipt.scopeFingerprint,
|
|
156
|
+
decision: {
|
|
157
|
+
strategy: receipt.decision.strategy,
|
|
158
|
+
reason: receipt.decision.reason,
|
|
159
|
+
workGroups: receipt.decision.workGroups.filter((g) => g.status === "pending" || g.status === "dispatched" || g.status === "running"),
|
|
160
|
+
validationExpectations: receipt.decision.validationExpectations,
|
|
161
|
+
confidence: receipt.decision.confidence,
|
|
162
|
+
jevUsed: receipt.decision.jevUsed,
|
|
163
|
+
reassessmentTriggers: receipt.decision.reassessmentTriggers,
|
|
164
|
+
},
|
|
165
|
+
createdAt: receipt.createdAt,
|
|
166
|
+
updatedAt: Date.now(),
|
|
167
|
+
status: receipt.status,
|
|
168
|
+
childIds: receipt.childIds,
|
|
169
|
+
pendingValidation: receipt.pendingValidation,
|
|
170
|
+
userConstraints: receipt.userConstraints,
|
|
171
|
+
};
|
|
172
|
+
}
|