pi-harness-runtime 1.1.451 → 1.1.452
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/AGENTS.md +53 -0
- package/CHANGELOG.md +24 -0
- package/index.ts +172 -21
- package/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/dist/continuation-status.d.ts +31 -0
- package/packages/jev-judge/dist/continuation-status.d.ts.map +1 -0
- package/packages/jev-judge/dist/continuation-status.js +40 -0
- package/packages/jev-judge/dist/continuation-status.js.map +1 -0
- package/packages/jev-judge/dist/delegation-harness.d.ts +74 -1
- package/packages/jev-judge/dist/delegation-harness.d.ts.map +1 -1
- package/packages/jev-judge/dist/delegation-harness.js +183 -18
- package/packages/jev-judge/dist/delegation-harness.js.map +1 -1
- package/packages/jev-judge/dist/delegation-scope.d.ts +88 -0
- package/packages/jev-judge/dist/delegation-scope.d.ts.map +1 -0
- package/packages/jev-judge/dist/delegation-scope.js +218 -0
- package/packages/jev-judge/dist/delegation-scope.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-status.ts +41 -0
- package/packages/jev-judge/src/delegation-harness.ts +254 -45
- package/packages/jev-judge/src/delegation-scope.ts +264 -0
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/prompt-compiler/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/relay/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/dist/classify.d.ts +22 -1
- package/packages/two-phase-review/dist/classify.d.ts.map +1 -1
- package/packages/two-phase-review/dist/classify.js +37 -1
- package/packages/two-phase-review/dist/classify.js.map +1 -1
- package/packages/two-phase-review/dist/index.d.ts +4 -2
- package/packages/two-phase-review/dist/index.d.ts.map +1 -1
- package/packages/two-phase-review/dist/index.js +3 -2
- package/packages/two-phase-review/dist/index.js.map +1 -1
- package/packages/two-phase-review/dist/rules.d.ts +8 -3
- package/packages/two-phase-review/dist/rules.d.ts.map +1 -1
- package/packages/two-phase-review/dist/rules.js +36 -5
- package/packages/two-phase-review/dist/rules.js.map +1 -1
- package/packages/two-phase-review/dist/runtime.d.ts +9 -0
- package/packages/two-phase-review/dist/runtime.d.ts.map +1 -1
- package/packages/two-phase-review/dist/runtime.js +68 -17
- package/packages/two-phase-review/dist/runtime.js.map +1 -1
- package/packages/two-phase-review/dist/state.d.ts +15 -0
- package/packages/two-phase-review/dist/state.d.ts.map +1 -1
- package/packages/two-phase-review/dist/state.js +37 -2
- package/packages/two-phase-review/dist/state.js.map +1 -1
- package/packages/two-phase-review/dist/types.d.ts +2 -2
- package/packages/two-phase-review/dist/types.d.ts.map +1 -1
- package/packages/two-phase-review/dist/validation.d.ts +50 -0
- package/packages/two-phase-review/dist/validation.d.ts.map +1 -0
- package/packages/two-phase-review/dist/validation.js +54 -0
- package/packages/two-phase-review/dist/validation.js.map +1 -0
- package/packages/two-phase-review/package.json +1 -1
- package/packages/two-phase-review/src/classify.ts +38 -1
- package/packages/two-phase-review/src/index.ts +16 -0
- package/packages/two-phase-review/src/rules.ts +48 -4
- package/packages/two-phase-review/src/runtime.ts +88 -17
- package/packages/two-phase-review/src/state.ts +38 -2
- package/packages/two-phase-review/src/types.ts +2 -1
- package/packages/two-phase-review/src/validation.ts +86 -0
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The measured scope of a task.
|
|
3
|
+
*
|
|
4
|
+
* A delegation decision is only worth something when it is based on the
|
|
5
|
+
* work that really has to be done. The numbers of a request are not a
|
|
6
|
+
* measurement: "refactor everything" says nothing about how many files
|
|
7
|
+
* are affected. This module measures the scope in the workspace:
|
|
8
|
+
*
|
|
9
|
+
* - The files of the workspace that the request actually names.
|
|
10
|
+
* - The files the session has already changed.
|
|
11
|
+
* - The groups those files form (one group per package or directory).
|
|
12
|
+
* - The tracked files that reference the measured groups.
|
|
13
|
+
*
|
|
14
|
+
* Every number in the result is a count of something real. When nothing
|
|
15
|
+
* can be measured the result is empty, and the caller has to say so
|
|
16
|
+
* instead of inventing a scope.
|
|
17
|
+
*/
|
|
18
|
+
import { execFileSync } from "node:child_process";
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
/** The default upper bound of the files of one measurement. */
|
|
21
|
+
const DEFAULT_LIMIT = 400;
|
|
22
|
+
/** The maximum number of groups that are searched for references. */
|
|
23
|
+
const MAX_REFERENCE_GROUPS = 10;
|
|
24
|
+
/**
|
|
25
|
+
* Run a git command in the workspace and return its output.
|
|
26
|
+
*
|
|
27
|
+
* A command that fails is not a measurement: the caller gets an empty
|
|
28
|
+
* result instead of a guess.
|
|
29
|
+
*/
|
|
30
|
+
function git(workspacePath, args) {
|
|
31
|
+
try {
|
|
32
|
+
return execFileSync("git", args, {
|
|
33
|
+
cwd: workspacePath,
|
|
34
|
+
encoding: "utf8",
|
|
35
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
36
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The tracked files of the workspace.
|
|
45
|
+
*/
|
|
46
|
+
function trackedFiles(workspacePath) {
|
|
47
|
+
const out = git(workspacePath, ["ls-files", "-z"]);
|
|
48
|
+
if (!out)
|
|
49
|
+
return [];
|
|
50
|
+
return out.split("\0").filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The files the working tree has changed.
|
|
54
|
+
*/
|
|
55
|
+
export function changedFiles(workspacePath) {
|
|
56
|
+
const out = git(workspacePath, ["status", "--porcelain", "-z"]);
|
|
57
|
+
if (!out)
|
|
58
|
+
return [];
|
|
59
|
+
const files = [];
|
|
60
|
+
for (const entry of out.split("\0")) {
|
|
61
|
+
if (!entry)
|
|
62
|
+
continue;
|
|
63
|
+
// The porcelain format is "XY path"; a rename carries two paths.
|
|
64
|
+
const path = entry.slice(3).trim();
|
|
65
|
+
if (!path)
|
|
66
|
+
continue;
|
|
67
|
+
if (path.includes(" -> ")) {
|
|
68
|
+
const parts = path.split(" -> ");
|
|
69
|
+
files.push(parts[0].trim(), parts[1].trim());
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
files.push(path);
|
|
73
|
+
}
|
|
74
|
+
return files.filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The group a file belongs to: its package, or its top directory.
|
|
78
|
+
*/
|
|
79
|
+
export function moduleOf(file) {
|
|
80
|
+
const parts = file.split("/");
|
|
81
|
+
if (parts[0] === "packages" && parts.length > 1)
|
|
82
|
+
return `packages/${parts[1]}`;
|
|
83
|
+
return parts[0];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The extension of a file, without the dot.
|
|
87
|
+
*/
|
|
88
|
+
export function extensionOf(file) {
|
|
89
|
+
const name = file.split("/").pop() ?? file;
|
|
90
|
+
const dot = name.lastIndexOf(".");
|
|
91
|
+
if (dot <= 0)
|
|
92
|
+
return "";
|
|
93
|
+
return name.slice(dot + 1).toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Compare two paths.
|
|
97
|
+
*/
|
|
98
|
+
function comparePaths(a, b) {
|
|
99
|
+
if (a === b)
|
|
100
|
+
return 0;
|
|
101
|
+
return a < b ? -1 : 1;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Group the measured files.
|
|
105
|
+
*/
|
|
106
|
+
export function groupFiles(files) {
|
|
107
|
+
const groups = new Map();
|
|
108
|
+
for (const file of files) {
|
|
109
|
+
const id = moduleOf(file);
|
|
110
|
+
const list = groups.get(id) ?? [];
|
|
111
|
+
list.push(file);
|
|
112
|
+
groups.set(id, list);
|
|
113
|
+
}
|
|
114
|
+
return [...groups.entries()]
|
|
115
|
+
.map(([id, groupFiles]) => ({ id, files: groupFiles.sort(comparePaths) }))
|
|
116
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The number of tracked files that reference the groups.
|
|
120
|
+
*
|
|
121
|
+
* The count is a real count of the files that mention the path of the
|
|
122
|
+
* group, so it is a lower bound of the coupling, not a guess.
|
|
123
|
+
*/
|
|
124
|
+
function countCallSites(workspacePath, groups) {
|
|
125
|
+
let count = 0;
|
|
126
|
+
for (const group of groups.slice(0, MAX_REFERENCE_GROUPS)) {
|
|
127
|
+
const out = git(workspacePath, ["grep", "-l", "-F", "--", group.id]);
|
|
128
|
+
if (!out)
|
|
129
|
+
continue;
|
|
130
|
+
const files = out.split("\n").filter(Boolean);
|
|
131
|
+
// The files of the group mention their own path: they are not
|
|
132
|
+
// references to the group.
|
|
133
|
+
count += files.filter((file) => moduleOf(file) !== group.id).length;
|
|
134
|
+
}
|
|
135
|
+
return count;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A stable fingerprint of a set of files.
|
|
139
|
+
*/
|
|
140
|
+
export function fingerprintOf(files) {
|
|
141
|
+
const hash = createHash("sha256");
|
|
142
|
+
hash.update([...files].sort(comparePaths).join("\n"));
|
|
143
|
+
return hash.digest("hex").slice(0, 16);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The path fragments of a request.
|
|
147
|
+
*
|
|
148
|
+
* A hint is a place in the repository that the request names: a path with
|
|
149
|
+
* a separator, or a file name with a known extension. The measurement
|
|
150
|
+
* checks every hint against the tracked files, so a hint that names
|
|
151
|
+
* nothing is simply not used.
|
|
152
|
+
*/
|
|
153
|
+
export function scopeHintsFromText(text) {
|
|
154
|
+
const hints = new Set();
|
|
155
|
+
for (const match of text.match(/[\w.-]+(?:\/[\w.-]+)+/g) ?? []) {
|
|
156
|
+
hints.add(match.replace(/^\.\//, ""));
|
|
157
|
+
}
|
|
158
|
+
for (const match of text.match(/[\w-]+\.(?:ts|tsx|mts|cts|js|mjs|cjs|json|md|py|sh|yaml|yml)\b/g) ?? []) {
|
|
159
|
+
hints.add(match);
|
|
160
|
+
}
|
|
161
|
+
return [...hints].slice(0, 8);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Measure the scope of a task in the workspace.
|
|
165
|
+
*/
|
|
166
|
+
export function measureScope(options) {
|
|
167
|
+
const workspacePath = options.workspacePath;
|
|
168
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
169
|
+
const tracked = trackedFiles(workspacePath);
|
|
170
|
+
const hints = (options.hints ?? []).map((hint) => hint.trim()).filter(Boolean);
|
|
171
|
+
// The files the request names: a real match against the tracked paths.
|
|
172
|
+
const matchedHints = [];
|
|
173
|
+
const named = new Set();
|
|
174
|
+
for (const hint of hints) {
|
|
175
|
+
const needle = hint.toLowerCase();
|
|
176
|
+
let matched = false;
|
|
177
|
+
for (const file of tracked) {
|
|
178
|
+
if (!file.toLowerCase().includes(needle))
|
|
179
|
+
continue;
|
|
180
|
+
matched = true;
|
|
181
|
+
if (named.size < limit)
|
|
182
|
+
named.add(file);
|
|
183
|
+
}
|
|
184
|
+
if (matched)
|
|
185
|
+
matchedHints.push(hint);
|
|
186
|
+
}
|
|
187
|
+
// The files the session has changed: the scope that is already real.
|
|
188
|
+
const changed = (options.changedFiles ?? changedFiles(workspacePath)).filter((file) => tracked.includes(file));
|
|
189
|
+
let files = [];
|
|
190
|
+
let source = "none";
|
|
191
|
+
if (named.size > 0) {
|
|
192
|
+
files = [...named];
|
|
193
|
+
source = "request";
|
|
194
|
+
}
|
|
195
|
+
else if (changed.length > 0) {
|
|
196
|
+
files = changed.slice(0, limit);
|
|
197
|
+
source = "working-tree";
|
|
198
|
+
}
|
|
199
|
+
const groups = groupFiles(files);
|
|
200
|
+
const fileTypes = {};
|
|
201
|
+
for (const file of files) {
|
|
202
|
+
const extension = extensionOf(file);
|
|
203
|
+
if (!extension)
|
|
204
|
+
continue;
|
|
205
|
+
fileTypes[extension] = (fileTypes[extension] ?? 0) + 1;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
files: files.sort(comparePaths),
|
|
209
|
+
groups,
|
|
210
|
+
workspaceFiles: tracked.length,
|
|
211
|
+
callSites: countCallSites(workspacePath, groups),
|
|
212
|
+
fileTypes,
|
|
213
|
+
fingerprint: fingerprintOf(files),
|
|
214
|
+
matchedHints,
|
|
215
|
+
source,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=delegation-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation-scope.js","sourceRoot":"","sources":["../src/delegation-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lifecycle of a continuation that the harness dispatched.
|
|
3
|
+
*
|
|
4
|
+
* A continuation is a turn that the harness started itself. While that
|
|
5
|
+
* turn runs, the status says so: a timer that clears the status while the
|
|
6
|
+
* agent is still working would claim that nothing runs any more.
|
|
7
|
+
*
|
|
8
|
+
* The gate is the only thing that decides whether a continuation is still
|
|
9
|
+
* pending, so it can be read and tested without a session.
|
|
10
|
+
*/
|
|
11
|
+
export class ContinuationGate {
|
|
12
|
+
private inFlight = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A continuation was dispatched.
|
|
16
|
+
*
|
|
17
|
+
* The status stays "RUN" from here until the turn settles.
|
|
18
|
+
*/
|
|
19
|
+
begin(): void {
|
|
20
|
+
this.inFlight = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The turn settled.
|
|
25
|
+
*
|
|
26
|
+
* @returns True when a continuation was still pending, so the caller
|
|
27
|
+
* has to clear the status. False when there was nothing to clear.
|
|
28
|
+
*/
|
|
29
|
+
settle(): boolean {
|
|
30
|
+
if (!this.inFlight) return false;
|
|
31
|
+
this.inFlight = false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether a continuation is still running.
|
|
37
|
+
*/
|
|
38
|
+
get pending(): boolean {
|
|
39
|
+
return this.inFlight;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -9,17 +9,34 @@ import { homedir as osHomedir } from "node:os";
|
|
|
9
9
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
12
|
+
import type { ScopeMeasurement, ScopeWorkGroup } from "./delegation-scope.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Extension capability for delegation (inline to avoid dependency)
|
|
15
16
|
*/
|
|
16
17
|
export interface ExtensionCapability {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
name: string;
|
|
19
|
+
/** The tool that can dispatch a child. */
|
|
20
|
+
toolName: string;
|
|
21
|
+
supportsSubagents: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The schema of the tool carries a parameter that isolates the work of
|
|
24
|
+
* a child. Without such a parameter the claim is not made.
|
|
25
|
+
*/
|
|
26
|
+
supportsWorkIsolation: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The schema of the tool carries a parameter that stops a child. Without
|
|
29
|
+
* such a parameter the claim is not made.
|
|
30
|
+
*/
|
|
31
|
+
supportsCancellation: boolean;
|
|
32
|
+
availableTools: string[];
|
|
33
|
+
/**
|
|
34
|
+
* The schema of the tool was read and the tool is active.
|
|
35
|
+
*
|
|
36
|
+
* This is not a proof that a child ever ran: it says that the session
|
|
37
|
+
* can ask for one. Only a dispatch that was really sent proves that.
|
|
38
|
+
*/
|
|
39
|
+
verified: boolean;
|
|
23
40
|
}
|
|
24
41
|
|
|
25
42
|
/**
|
|
@@ -36,29 +53,52 @@ export type DelegationStrategy =
|
|
|
36
53
|
* Delegation decision
|
|
37
54
|
*/
|
|
38
55
|
export interface DelegationDecision {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
strategy: DelegationStrategy;
|
|
57
|
+
reason: string;
|
|
58
|
+
/**
|
|
59
|
+
* The groups the work falls into. They are empty unless the scope was
|
|
60
|
+
* measured: a decision without groups is not a delegation plan.
|
|
61
|
+
*/
|
|
62
|
+
workGroups: ScopeWorkGroup[];
|
|
63
|
+
validationExpectations: string;
|
|
64
|
+
confidence: "high" | "medium" | "low";
|
|
65
|
+
jevUsed: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Whether a child was really dispatched.
|
|
68
|
+
*
|
|
69
|
+
* The harness assesses the work, it does not run it: the agent is the
|
|
70
|
+
* one that sends the groups to the sub-agent tool.
|
|
71
|
+
*/
|
|
72
|
+
dispatched: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The name of the active tool that can dispatch a child.
|
|
75
|
+
*
|
|
76
|
+
* It is empty when no such tool is active. The name is the proof that
|
|
77
|
+
* the dispatch is possible, not the proof that it happened.
|
|
78
|
+
*/
|
|
79
|
+
dispatchTool?: string;
|
|
80
|
+
reassessmentTriggers: string[];
|
|
46
81
|
}
|
|
47
82
|
|
|
48
83
|
/**
|
|
49
84
|
* Scope facts
|
|
50
85
|
*/
|
|
51
86
|
export interface ScopeFacts {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
estimatedFiles: number;
|
|
88
|
+
observedFiles: number;
|
|
89
|
+
estimatedCallSites: number;
|
|
90
|
+
observedCallSites: number;
|
|
91
|
+
independentComponents: number;
|
|
92
|
+
tightlyCoupled: boolean;
|
|
93
|
+
isMechanical: boolean;
|
|
94
|
+
isComplex: boolean;
|
|
95
|
+
fileTypes: Record<string, number>;
|
|
96
|
+
fingerprint: string;
|
|
97
|
+
/**
|
|
98
|
+
* The measured groups of the work. A delegation decision needs them:
|
|
99
|
+
* without groups there is nothing to hand to a child.
|
|
100
|
+
*/
|
|
101
|
+
workGroups: ScopeWorkGroup[];
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
/**
|
|
@@ -150,9 +190,149 @@ interface DelegationState {
|
|
|
150
190
|
}
|
|
151
191
|
|
|
152
192
|
/**
|
|
153
|
-
*
|
|
193
|
+
* The number of groups that can be handed to children at once.
|
|
194
|
+
*
|
|
195
|
+
* More groups than that is not a delegation plan: it is a list of files
|
|
196
|
+
* that still needs a decision about the order.
|
|
154
197
|
*/
|
|
155
|
-
const
|
|
198
|
+
const MAX_DELEGATED_GROUPS = 8;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build the scope facts from a measurement.
|
|
202
|
+
*
|
|
203
|
+
* Every number is a count of something the measurement found. The
|
|
204
|
+
* estimate is the size of the workspace: it is a real number, and it says
|
|
205
|
+
* how much of the repository the measured files are.
|
|
206
|
+
*/
|
|
207
|
+
export function scopeFactsFromMeasurement(measurement: ScopeMeasurement): ScopeFacts {
|
|
208
|
+
const groups = measurement.groups;
|
|
209
|
+
const extensions = Object.keys(measurement.fileTypes);
|
|
210
|
+
return {
|
|
211
|
+
estimatedFiles: measurement.workspaceFiles,
|
|
212
|
+
observedFiles: measurement.files.length,
|
|
213
|
+
estimatedCallSites: measurement.callSites,
|
|
214
|
+
observedCallSites: measurement.callSites,
|
|
215
|
+
independentComponents: groups.length,
|
|
216
|
+
tightlyCoupled: groups.length <= 1,
|
|
217
|
+
// A mechanical change touches one kind of file, and there is more
|
|
218
|
+
// than one of them: a rename across many files is mechanical, a
|
|
219
|
+
// single file is just work.
|
|
220
|
+
isMechanical: extensions.length === 1 && measurement.files.length >= 3,
|
|
221
|
+
// A complex change spans more than one module.
|
|
222
|
+
isComplex: groups.length > 1,
|
|
223
|
+
fileTypes: measurement.fileTypes,
|
|
224
|
+
fingerprint: measurement.fingerprint,
|
|
225
|
+
workGroups: groups,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The reason why the work stays in the session.
|
|
231
|
+
*/
|
|
232
|
+
function singleAgentReason(groups: ScopeWorkGroup[]): string {
|
|
233
|
+
if (groups.length === 1) {
|
|
234
|
+
return `One measured group (${groups[0].id}): the work stays in this session`;
|
|
235
|
+
}
|
|
236
|
+
if (groups.length > MAX_DELEGATED_GROUPS) {
|
|
237
|
+
return `${groups.length} measured groups: too many to hand out at once, the work stays in this session`;
|
|
238
|
+
}
|
|
239
|
+
return "No measured group of work: the work stays in this session";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Build the message that hands a delegation plan to the agent.
|
|
244
|
+
*
|
|
245
|
+
* The message names the measured groups and the tool that can dispatch a
|
|
246
|
+
* child. It does not claim that anything was dispatched: the harness
|
|
247
|
+
* measured the work, the agent is the one that sends the groups out.
|
|
248
|
+
*
|
|
249
|
+
* @param decision The decision of the assessment.
|
|
250
|
+
* @returns The message, or null when there is nothing to hand out.
|
|
251
|
+
*/
|
|
252
|
+
export function buildDelegationSteer(
|
|
253
|
+
decision: {
|
|
254
|
+
strategy: string;
|
|
255
|
+
workGroups?: { id: string; files: string[] }[];
|
|
256
|
+
dispatchTool?: string;
|
|
257
|
+
} | null,
|
|
258
|
+
): string | null {
|
|
259
|
+
if (!decision) return null;
|
|
260
|
+
if (decision.strategy !== "delegate") return null;
|
|
261
|
+
const groups = decision.workGroups ?? [];
|
|
262
|
+
if (groups.length === 0) return null;
|
|
263
|
+
|
|
264
|
+
const lines = [
|
|
265
|
+
`[Delegation plan] The measured scope of this request falls into ${groups.length} independent group(s):`,
|
|
266
|
+
...groups.map((group) => `- ${group.id} (${group.files.length} file(s))`),
|
|
267
|
+
];
|
|
268
|
+
if (decision.dispatchTool) {
|
|
269
|
+
lines.push(
|
|
270
|
+
`Send each group to a child agent with the ${decision.dispatchTool} tool, then integrate the results and validate them.`,
|
|
271
|
+
);
|
|
272
|
+
} else {
|
|
273
|
+
lines.push("Send each group to a child agent, then integrate the results and validate them.");
|
|
274
|
+
}
|
|
275
|
+
lines.push("The harness measured the scope; it did not dispatch the groups itself.");
|
|
276
|
+
return lines.join("\n");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The parameters of a tool that dispatch a child.
|
|
281
|
+
*
|
|
282
|
+
* A tool that takes the task of a child can dispatch one. The name of the
|
|
283
|
+
* tool says nothing about that: only the schema does.
|
|
284
|
+
*/
|
|
285
|
+
const DISPATCH_PARAMETERS = ["task", "prompt", "instructions", "description"];
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The parameters of a tool that isolate the work of a child.
|
|
289
|
+
*/
|
|
290
|
+
const ISOLATION_PARAMETERS = ["cwd", "worktree", "isolation", "sandbox", "branch"];
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The parameters of a tool that stop a child.
|
|
294
|
+
*/
|
|
295
|
+
const CANCELLATION_PARAMETERS = ["timeout", "timeoutMs", "cancel", "signal", "abort"];
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The words that name a child agent.
|
|
299
|
+
*/
|
|
300
|
+
const CHILD_WORDS = ["subagent", "sub-agent", "child agent", "delegate", "spawn"];
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The property names of a tool schema.
|
|
304
|
+
*
|
|
305
|
+
* A schema is a plain object: the properties of the object, of every
|
|
306
|
+
* branch of a union, and of every item of an array.
|
|
307
|
+
*/
|
|
308
|
+
export function schemaPropertyNames(schema: unknown): Set<string> {
|
|
309
|
+
const names = new Set<string>();
|
|
310
|
+
const visit = (node: unknown): void => {
|
|
311
|
+
if (!node || typeof node !== "object") return;
|
|
312
|
+
const record = node as Record<string, unknown>;
|
|
313
|
+
const properties = record.properties;
|
|
314
|
+
if (properties && typeof properties === "object") {
|
|
315
|
+
for (const name of Object.keys(properties as Record<string, unknown>)) {
|
|
316
|
+
names.add(name);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
for (const branch of ["anyOf", "oneOf", "allOf"]) {
|
|
320
|
+
const list = record[branch];
|
|
321
|
+
if (Array.isArray(list)) for (const entry of list) visit(entry);
|
|
322
|
+
}
|
|
323
|
+
const items = record.items;
|
|
324
|
+
if (items) visit(items);
|
|
325
|
+
};
|
|
326
|
+
visit(schema);
|
|
327
|
+
return names;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The text of a tool that names what the tool is for.
|
|
332
|
+
*/
|
|
333
|
+
function toolText(tool: { name?: string; description?: string }): string {
|
|
334
|
+
return `${tool.name ?? ""} ${tool.description ?? ""}`.toLowerCase();
|
|
335
|
+
}
|
|
156
336
|
|
|
157
337
|
/**
|
|
158
338
|
* Create delegation harness integration
|
|
@@ -173,29 +353,42 @@ export function createDelegationHarness(
|
|
|
173
353
|
|
|
174
354
|
/**
|
|
175
355
|
* Discover and cache delegation capability
|
|
356
|
+
*
|
|
357
|
+
* The capability is read from the schema of the tools, not from their
|
|
358
|
+
* names: a tool that carries the task of a child can dispatch one, and
|
|
359
|
+
* only a schema that carries a working directory or a timeout proves
|
|
360
|
+
* that the work can be isolated or stopped.
|
|
176
361
|
*/
|
|
177
362
|
async function discoverCapability(): Promise<ExtensionCapability | null> {
|
|
178
363
|
if (state.capability) return state.capability;
|
|
179
364
|
|
|
180
365
|
try {
|
|
181
366
|
const allTools = pi.getAllTools();
|
|
182
|
-
const
|
|
183
|
-
const activeTools = pi.getActiveTools();
|
|
367
|
+
const activeTools = new Set(pi.getActiveTools());
|
|
184
368
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
369
|
+
for (const tool of allTools) {
|
|
370
|
+
const properties = schemaPropertyNames(tool.parameters);
|
|
371
|
+
const dispatches = DISPATCH_PARAMETERS.some((parameter) => properties.has(parameter));
|
|
372
|
+
if (!dispatches) continue;
|
|
373
|
+
// The tool has to be about a child agent: a schema that takes a
|
|
374
|
+
// task is not enough when the tool is something else entirely.
|
|
375
|
+
const text = toolText(tool);
|
|
376
|
+
if (!CHILD_WORDS.some((word) => text.includes(word))) continue;
|
|
377
|
+
|
|
378
|
+
const isolation = ISOLATION_PARAMETERS.find((parameter) => properties.has(parameter));
|
|
379
|
+
const cancellation = CANCELLATION_PARAMETERS.find((parameter) => properties.has(parameter));
|
|
380
|
+
const isActive = activeTools.has(tool.name);
|
|
188
381
|
|
|
189
|
-
if (hasSubagentTool) {
|
|
190
382
|
state.capability = {
|
|
191
|
-
name:
|
|
383
|
+
name: tool.name,
|
|
384
|
+
toolName: tool.name,
|
|
192
385
|
supportsSubagents: true,
|
|
193
|
-
supportsWorkIsolation:
|
|
194
|
-
supportsCancellation:
|
|
195
|
-
availableTools:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
verified:
|
|
386
|
+
supportsWorkIsolation: isolation !== undefined,
|
|
387
|
+
supportsCancellation: cancellation !== undefined,
|
|
388
|
+
availableTools: isActive ? [tool.name] : [],
|
|
389
|
+
// The capability is verified when the tool is active: an inactive
|
|
390
|
+
// tool cannot dispatch anything.
|
|
391
|
+
verified: isActive,
|
|
199
392
|
};
|
|
200
393
|
return state.capability;
|
|
201
394
|
}
|
|
@@ -208,8 +401,13 @@ export function createDelegationHarness(
|
|
|
208
401
|
|
|
209
402
|
/**
|
|
210
403
|
* Check if delegation assessment should be triggered
|
|
404
|
+
*
|
|
405
|
+
* The thresholds are compared with the measured scope. A scope that was
|
|
406
|
+
* not measured is never assessed: an empty measurement is not a small
|
|
407
|
+
* task, it is an unknown one.
|
|
211
408
|
*/
|
|
212
409
|
function shouldAssess(facts: ScopeFacts): boolean {
|
|
410
|
+
if (facts.observedFiles <= 0) return false;
|
|
213
411
|
if (facts.observedFiles >= minFiles) return true;
|
|
214
412
|
if (facts.observedCallSites >= minCallSites) return true;
|
|
215
413
|
if (facts.independentComponents >= minComponents) return true;
|
|
@@ -218,6 +416,10 @@ export function createDelegationHarness(
|
|
|
218
416
|
|
|
219
417
|
/**
|
|
220
418
|
* Make deterministic assessment
|
|
419
|
+
*
|
|
420
|
+
* The decision is built from the measured scope and from the schema of
|
|
421
|
+
* the tools. A strategy that claims a delegation needs the groups of the
|
|
422
|
+
* measurement: without them there is nothing to hand to a child.
|
|
221
423
|
*/
|
|
222
424
|
function makeDeterministicAssessment(
|
|
223
425
|
facts: ScopeFacts,
|
|
@@ -226,11 +428,12 @@ export function createDelegationHarness(
|
|
|
226
428
|
if (!capability?.verified) {
|
|
227
429
|
return {
|
|
228
430
|
strategy: "unavailable",
|
|
229
|
-
reason: "No
|
|
431
|
+
reason: "No active tool can dispatch a child agent",
|
|
230
432
|
workGroups: [],
|
|
231
433
|
validationExpectations: "Parent handles validation",
|
|
232
434
|
confidence: "high",
|
|
233
435
|
jevUsed: false,
|
|
436
|
+
dispatched: false,
|
|
234
437
|
reassessmentTriggers: ["Extension available"],
|
|
235
438
|
};
|
|
236
439
|
}
|
|
@@ -238,34 +441,40 @@ export function createDelegationHarness(
|
|
|
238
441
|
if (facts.isMechanical && !facts.isComplex) {
|
|
239
442
|
return {
|
|
240
443
|
strategy: "script_only",
|
|
241
|
-
reason:
|
|
444
|
+
reason: `Mechanical transformation of ${facts.observedFiles} measured file(s)`,
|
|
242
445
|
workGroups: [],
|
|
243
446
|
validationExpectations: "Script output validation",
|
|
244
447
|
confidence: "high",
|
|
245
448
|
jevUsed: false,
|
|
449
|
+
dispatched: false,
|
|
246
450
|
reassessmentTriggers: ["Transformation unreliable"],
|
|
247
451
|
};
|
|
248
452
|
}
|
|
249
453
|
|
|
250
|
-
|
|
454
|
+
const groups = facts.workGroups ?? [];
|
|
455
|
+
if (groups.length >= 2 && groups.length <= MAX_DELEGATED_GROUPS && !facts.tightlyCoupled) {
|
|
251
456
|
return {
|
|
252
457
|
strategy: "delegate",
|
|
253
|
-
reason: `${facts.
|
|
254
|
-
workGroups:
|
|
458
|
+
reason: `${groups.length} measured groups over ${facts.observedFiles} file(s); the agent dispatches the groups`,
|
|
459
|
+
workGroups: groups,
|
|
255
460
|
validationExpectations: "Each group validated, then integrated",
|
|
256
461
|
confidence: "medium",
|
|
257
462
|
jevUsed: false,
|
|
463
|
+
// The harness measured the groups, it did not dispatch them.
|
|
464
|
+
dispatched: false,
|
|
465
|
+
dispatchTool: capability.toolName,
|
|
258
466
|
reassessmentTriggers: ["Scope expands", "Coupling discovered"],
|
|
259
467
|
};
|
|
260
468
|
}
|
|
261
469
|
|
|
262
470
|
return {
|
|
263
471
|
strategy: "single_agent",
|
|
264
|
-
reason:
|
|
265
|
-
workGroups:
|
|
472
|
+
reason: singleAgentReason(groups),
|
|
473
|
+
workGroups: groups,
|
|
266
474
|
validationExpectations: "Parent validates",
|
|
267
475
|
confidence: "low",
|
|
268
476
|
jevUsed: false,
|
|
477
|
+
dispatched: false,
|
|
269
478
|
reassessmentTriggers: ["Scope exceeds threshold"],
|
|
270
479
|
};
|
|
271
480
|
}
|