ultimate-pi 0.19.1 → 0.22.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.
- package/.agents/skills/harness-decisions/SKILL.md +68 -2
- package/.agents/skills/harness-git-commit/SKILL.md +72 -0
- package/.agents/skills/harness-governor/SKILL.md +2 -2
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
- package/.agents/skills/harness-plan/SKILL.md +13 -11
- package/.agents/skills/harness-review/SKILL.md +1 -1
- package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
- package/.agents/skills/sentrux/SKILL.md +4 -2
- package/.agents/skills/wiki-save/SKILL.md +1 -1
- package/.pi/PACKAGING.md +6 -0
- package/.pi/SYSTEM.md +21 -3
- package/.pi/agents/harness/ls-lint-steward.md +49 -0
- package/.pi/agents/harness/planning/decompose.md +4 -4
- package/.pi/agents/harness/reviewing/evaluator.md +1 -1
- package/.pi/agents/harness/running/executor.md +43 -2
- package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
- package/.pi/agents/pi-pi/prompt-expert.md +17 -2
- package/.pi/auto-commit.json +9 -2
- package/.pi/extensions/debate-orchestrator.ts +3 -0
- package/.pi/extensions/harness-anchored-edit.ts +139 -0
- package/.pi/extensions/harness-ask-user.ts +13 -34
- package/.pi/extensions/harness-debate-tools.ts +43 -4
- package/.pi/extensions/harness-live-widget.ts +28 -19
- package/.pi/extensions/harness-run-context.ts +278 -115
- package/.pi/extensions/harness-web-tools.ts +598 -471
- package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
- package/.pi/extensions/observation-bus.ts +4 -0
- package/.pi/extensions/policy-gate.ts +270 -229
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/soundboard.ts +48 -48
- package/.pi/harness/README.md +4 -0
- package/.pi/harness/agents.manifest.json +15 -7
- package/.pi/harness/agents.policy.yaml +47 -81
- package/.pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md +41 -0
- package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
- package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
- package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
- package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
- package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
- package/.pi/harness/docs/adrs/README.md +7 -0
- package/.pi/harness/docs/practice-map.md +21 -5
- package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
- package/.pi/harness/evolution/self-healing-rules.json +16 -0
- package/.pi/harness/ls-lint/naming.manifest.json +128 -0
- package/.pi/harness/sentrux/architecture.manifest.json +1 -1
- package/.pi/harness/specs/auto-commit.schema.json +63 -0
- package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
- package/.pi/harness/specs/naming-manifest.schema.json +54 -0
- package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
- package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
- package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
- package/.pi/harness/specs/sentrux-report.schema.json +119 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
- package/.pi/lib/agents-policy.d.mts +26 -47
- package/.pi/lib/agents-policy.mjs +84 -29
- package/.pi/lib/agents-policy.ts +1 -0
- package/.pi/lib/agt/build-evaluation-context.ts +136 -64
- package/.pi/lib/ask-user/constants.mjs +3 -0
- package/.pi/lib/ask-user/constants.ts +4 -0
- package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
- package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
- package/.pi/lib/ask-user/dialog.ts +2 -314
- package/.pi/lib/ask-user/fallback.ts +2 -78
- package/.pi/lib/ask-user/format.ts +85 -0
- package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
- package/.pi/lib/ask-user/index.ts +114 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
- package/.pi/lib/ask-user/policy.mjs +43 -0
- package/.pi/lib/ask-user/policy.ts +104 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
- package/.pi/lib/ask-user/presenters/headless.ts +131 -0
- package/.pi/lib/ask-user/presenters/select.ts +60 -0
- package/.pi/lib/ask-user/presenters/tui.ts +373 -0
- package/.pi/lib/ask-user/presenters/types.ts +13 -0
- package/.pi/lib/ask-user/render.ts +40 -9
- package/.pi/lib/ask-user/schema.ts +66 -13
- package/.pi/lib/ask-user/types.ts +60 -3
- package/.pi/lib/ask-user/validate-core.mjs +193 -7
- package/.pi/lib/ask-user/validate.ts +53 -34
- package/.pi/lib/harness-anchored-edit/.hash_anchors +1721 -0
- package/.pi/lib/harness-anchored-edit/anchor-state.ts +320 -0
- package/.pi/lib/harness-anchored-edit/apply-anchored-edits.ts +161 -0
- package/.pi/lib/harness-anchored-edit/edit-executor.ts +146 -0
- package/.pi/lib/harness-anchored-edit/index.ts +9 -0
- package/.pi/lib/harness-anchored-edit/line-protocol.ts +38 -0
- package/.pi/lib/harness-anchored-edit/package.json +3 -0
- package/.pi/lib/harness-anchored-edit/settings.ts +1 -0
- package/.pi/lib/harness-anchored-edit/task-id.ts +8 -0
- package/.pi/lib/harness-anchored-edit/types.ts +19 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -21
- package/.pi/lib/harness-auto-commit-config.mjs +321 -0
- package/.pi/lib/harness-lens/clients/anchored-edit-autopatch.ts +158 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +246 -96
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
- package/.pi/lib/harness-repair-brief.ts +84 -25
- package/.pi/lib/harness-run-context.ts +42 -52
- package/.pi/lib/harness-sentrux-parse.mjs +272 -0
- package/.pi/lib/harness-sentrux-root.mjs +78 -0
- package/.pi/lib/harness-slash-completions.ts +116 -0
- package/.pi/lib/harness-spawn-topology.ts +121 -87
- package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
- package/.pi/lib/harness-subagents-bridge.ts +11 -6
- package/.pi/lib/harness-ui-state.ts +95 -48
- package/.pi/lib/plan-approval/dialog.ts +5 -0
- package/.pi/lib/plan-approval/validate.ts +1 -1
- package/.pi/lib/plan-approval-readiness.ts +32 -0
- package/.pi/lib/plan-debate-gate.ts +154 -114
- package/.pi/lib/plan-task-clarification.ts +158 -0
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-ls-lint-steward.md +43 -0
- package/.pi/prompts/harness-plan.md +58 -8
- package/.pi/prompts/harness-review.md +40 -6
- package/.pi/prompts/harness-run.md +33 -11
- package/.pi/prompts/harness-setup.md +72 -3
- package/.pi/prompts/harness-steer.md +3 -2
- package/.pi/prompts/wiki-save.md +5 -4
- package/.pi/scripts/README.md +8 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
- package/.pi/scripts/harness-anchored-edit-smoke.mjs +45 -0
- package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
- package/.pi/scripts/harness-cli-verify.sh +47 -0
- package/.pi/scripts/harness-git-churn.mjs +77 -0
- package/.pi/scripts/harness-git-commit.mjs +173 -0
- package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
- package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
- package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
- package/.pi/scripts/harness-sentrux-report.mjs +256 -0
- package/.pi/scripts/harness-verify.mjs +347 -117
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +65 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +31 -0
- package/README.md +13 -4
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/package.json +8 -3
- package/vendor/pi-subagents/src/agents.ts +5 -0
- package/vendor/pi-subagents/src/subagents.ts +22 -3
- package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
- package/.pi/scripts/release.sh +0 -338
|
@@ -31,10 +31,26 @@ const MUTATING_TOOLS = new Set(["write", "edit"]);
|
|
|
31
31
|
|
|
32
32
|
const cache = new Map();
|
|
33
33
|
|
|
34
|
+
const EXTENSION_BUNDLE_MODULES = {
|
|
35
|
+
executor: [
|
|
36
|
+
"subagent-governance.ts",
|
|
37
|
+
"harness-anchored-edit.ts",
|
|
38
|
+
"harness-lens.ts",
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
34
42
|
export function packageAgentsPolicyPath(packageRoot) {
|
|
35
43
|
return join(packageRoot, ".pi", "harness", "agents.policy.yaml");
|
|
36
44
|
}
|
|
37
45
|
|
|
46
|
+
/** Absolute paths for subprocess `-e` loads (curated; avoids parent-only extensions). */
|
|
47
|
+
export function resolveExtensionBundlePaths(packageRoot, bundleName) {
|
|
48
|
+
const modules = EXTENSION_BUNDLE_MODULES[bundleName];
|
|
49
|
+
if (!modules) return [];
|
|
50
|
+
const extDir = join(packageRoot, ".pi", "extensions");
|
|
51
|
+
return modules.map((name) => join(extDir, name));
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
export function projectAgentsPolicyPath(projectRoot) {
|
|
39
55
|
return join(projectRoot, ".pi", "agents.policy.yaml");
|
|
40
56
|
}
|
|
@@ -52,12 +68,19 @@ function readYamlFile(path) {
|
|
|
52
68
|
}
|
|
53
69
|
}
|
|
54
70
|
|
|
71
|
+
function normalizeExtensionBundle(raw) {
|
|
72
|
+
if (typeof raw.extension_bundle !== "string") return undefined;
|
|
73
|
+
const bundle = raw.extension_bundle.trim();
|
|
74
|
+
return bundle.length > 0 ? bundle : undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
function normalizeKindEntry(raw) {
|
|
56
78
|
if (!raw || typeof raw !== "object") return null;
|
|
57
79
|
const tools = Array.isArray(raw.tools) ? raw.tools.map(String) : [];
|
|
58
80
|
return {
|
|
59
81
|
tools,
|
|
60
82
|
extensions: raw.extensions === false ? false : Boolean(raw.extensions),
|
|
83
|
+
extensionBundle: normalizeExtensionBundle(raw),
|
|
61
84
|
readOnly: raw.read_only === true,
|
|
62
85
|
maxTurns:
|
|
63
86
|
typeof raw.max_turns === "number" && raw.max_turns > 0
|
|
@@ -93,6 +116,7 @@ function normalizeAgentEntry(raw) {
|
|
|
93
116
|
: raw.extensions === true
|
|
94
117
|
? true
|
|
95
118
|
: undefined,
|
|
119
|
+
extensionBundle: normalizeExtensionBundle(raw),
|
|
96
120
|
maxTurns:
|
|
97
121
|
typeof raw.max_turns === "number" && raw.max_turns > 0
|
|
98
122
|
? raw.max_turns
|
|
@@ -162,10 +186,25 @@ export function resolveEffectiveTools(agentId, merged) {
|
|
|
162
186
|
for (const t of entry.toolsDeny ?? []) base.delete(t);
|
|
163
187
|
for (const t of BUILTIN_DENY_TOOLS) base.delete(t);
|
|
164
188
|
|
|
189
|
+
const extensionBundle =
|
|
190
|
+
entry.extensionBundle ?? kind.extensionBundle ?? undefined;
|
|
191
|
+
const extensionsFull =
|
|
192
|
+
!extensionBundle &&
|
|
193
|
+
(entry.extensions === true
|
|
194
|
+
? true
|
|
195
|
+
: entry.extensions === false
|
|
196
|
+
? false
|
|
197
|
+
: Boolean(kind.extensions));
|
|
198
|
+
const extensionsOff = !extensionsFull;
|
|
199
|
+
|
|
165
200
|
return {
|
|
166
201
|
kind: kindName,
|
|
167
202
|
effectiveTools: [...base],
|
|
168
|
-
extensionsOff
|
|
203
|
+
extensionsOff,
|
|
204
|
+
extensionBundle,
|
|
205
|
+
extensionsFull,
|
|
206
|
+
/** Suppress Pi builtins when harness read/edit register (full extensions or subprocess bundle). */
|
|
207
|
+
noBuiltinTools: extensionsFull || Boolean(extensionBundle),
|
|
169
208
|
readOnly: kind.readOnly,
|
|
170
209
|
maxTurns: entry.maxTurns ?? kind.maxTurns,
|
|
171
210
|
thinking: entry.thinking ?? kind.thinking,
|
|
@@ -223,7 +262,45 @@ function isMutatingBash(command) {
|
|
|
223
262
|
command,
|
|
224
263
|
);
|
|
225
264
|
}
|
|
265
|
+
function deniesReadOnlyBatchExecute(toolInput) {
|
|
266
|
+
const commands = toolInput.commands;
|
|
267
|
+
if (!Array.isArray(commands)) return false;
|
|
268
|
+
for (const c of commands) {
|
|
269
|
+
const cmd =
|
|
270
|
+
typeof c === "string" ? c : String(c?.command ?? c?.code ?? "");
|
|
271
|
+
if (cmd && isMutatingBash(cmd)) return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function deniesReadOnlyExecute(toolInput) {
|
|
277
|
+
const code = String(toolInput.code ?? toolInput.command ?? "");
|
|
278
|
+
return Boolean(code && isMutatingBash(code));
|
|
279
|
+
}
|
|
226
280
|
|
|
281
|
+
function deniesReadOnlyBash(agentId, toolInput) {
|
|
282
|
+
const command = String(toolInput.command ?? "");
|
|
283
|
+
if (command && isMutatingBash(command)) return true;
|
|
284
|
+
if (
|
|
285
|
+
isHarnessPlanningAgent(agentId) &&
|
|
286
|
+
command &&
|
|
287
|
+
PLANNING_ARTIFACT_JSON_WRITE.test(command)
|
|
288
|
+
) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
if (
|
|
292
|
+
isHarnessPlanningAgent(agentId) &&
|
|
293
|
+
command &&
|
|
294
|
+
PLANNING_BASH_DENY_PATTERNS.some((p) => p.test(command))
|
|
295
|
+
) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Manifest allowlist + subprocess constraints (replaces harness-subagent-policy.ts).
|
|
303
|
+
*/
|
|
227
304
|
/**
|
|
228
305
|
* Manifest allowlist + subprocess constraints (replaces harness-subagent-policy.ts).
|
|
229
306
|
*/
|
|
@@ -258,40 +335,15 @@ export function allowsAgentTool(input) {
|
|
|
258
335
|
if (MUTATING_TOOLS.has(toolName) && spec.readOnly) return false;
|
|
259
336
|
|
|
260
337
|
if (toolName === "ctx_batch_execute" && spec.readOnly) {
|
|
261
|
-
|
|
262
|
-
if (Array.isArray(commands)) {
|
|
263
|
-
for (const c of commands) {
|
|
264
|
-
const cmd =
|
|
265
|
-
typeof c === "string"
|
|
266
|
-
? c
|
|
267
|
-
: String(c?.command ?? c?.code ?? "");
|
|
268
|
-
if (cmd && isMutatingBash(cmd)) return false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
338
|
+
if (deniesReadOnlyBatchExecute(toolInput)) return false;
|
|
271
339
|
}
|
|
272
340
|
|
|
273
341
|
if (toolName === "ctx_execute" && spec.readOnly) {
|
|
274
|
-
|
|
275
|
-
if (code && isMutatingBash(code)) return false;
|
|
342
|
+
if (deniesReadOnlyExecute(toolInput)) return false;
|
|
276
343
|
}
|
|
277
344
|
|
|
278
345
|
if (toolName === "bash" && spec.readOnly) {
|
|
279
|
-
|
|
280
|
-
if (command && isMutatingBash(command)) return false;
|
|
281
|
-
if (
|
|
282
|
-
isHarnessPlanningAgent(agentId) &&
|
|
283
|
-
command &&
|
|
284
|
-
PLANNING_ARTIFACT_JSON_WRITE.test(command)
|
|
285
|
-
) {
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
288
|
-
if (
|
|
289
|
-
isHarnessPlanningAgent(agentId) &&
|
|
290
|
-
command &&
|
|
291
|
-
PLANNING_BASH_DENY_PATTERNS.some((p) => p.test(command))
|
|
292
|
-
) {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
346
|
+
if (deniesReadOnlyBash(agentId, toolInput)) return false;
|
|
295
347
|
}
|
|
296
348
|
|
|
297
349
|
return true;
|
|
@@ -304,6 +356,9 @@ export function applyAgentPolicyToConfig(agent, packageRoot, projectRoot) {
|
|
|
304
356
|
...agent,
|
|
305
357
|
tools: spec.effectiveTools.length > 0 ? spec.effectiveTools : undefined,
|
|
306
358
|
extensionsOff: spec.extensionsOff,
|
|
359
|
+
extensionBundle: spec.extensionBundle,
|
|
360
|
+
extensionsFull: spec.extensionsFull,
|
|
361
|
+
noBuiltinTools: spec.noBuiltinTools,
|
|
307
362
|
maxTurns: spec.maxTurns ?? agent.maxTurns,
|
|
308
363
|
thinking: spec.thinking ?? agent.thinking,
|
|
309
364
|
model: spec.model ?? agent.model,
|
package/.pi/lib/agents-policy.ts
CHANGED
|
@@ -125,6 +125,106 @@ function bashPlanningJsonDenied(command: string, agentType: string): boolean {
|
|
|
125
125
|
return PLANNING_ARTIFACT_JSON_WRITE.test(command);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
function isReadOnlyAgentKind(agentKind: string): boolean {
|
|
129
|
+
return (
|
|
130
|
+
agentKind === "planner" ||
|
|
131
|
+
agentKind === "evaluator" ||
|
|
132
|
+
agentKind === "adversary" ||
|
|
133
|
+
agentKind === "tie_breaker" ||
|
|
134
|
+
agentKind === "trace" ||
|
|
135
|
+
agentKind === "incident" ||
|
|
136
|
+
agentKind === "meta"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function resolvePlanMutation(args: {
|
|
141
|
+
sessionActive: boolean;
|
|
142
|
+
toolName: string;
|
|
143
|
+
toolInput: Record<string, unknown>;
|
|
144
|
+
phase: HarnessPhase;
|
|
145
|
+
runCtx: HarnessRunContext | null;
|
|
146
|
+
projectRoot: string;
|
|
147
|
+
aborted: boolean;
|
|
148
|
+
entries: unknown[];
|
|
149
|
+
sessionId: string;
|
|
150
|
+
}): Promise<{ allowed: boolean; reason?: string }> {
|
|
151
|
+
const mutatingTool = args.toolName === "write" || args.toolName === "edit";
|
|
152
|
+
if (!(args.sessionActive && mutatingTool)) return { allowed: true };
|
|
153
|
+
return isPlanPhaseAllowedMutation(
|
|
154
|
+
args.toolName,
|
|
155
|
+
args.toolInput,
|
|
156
|
+
args.phase,
|
|
157
|
+
args.runCtx,
|
|
158
|
+
args.projectRoot,
|
|
159
|
+
{
|
|
160
|
+
aborted: args.aborted,
|
|
161
|
+
entries: args.entries,
|
|
162
|
+
ownerSessionId: args.runCtx?.owner_pi_session_id,
|
|
163
|
+
currentSessionId: args.sessionId,
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resolveContextMode(args: {
|
|
169
|
+
sessionActive: boolean;
|
|
170
|
+
toolName: string;
|
|
171
|
+
toolInput: Record<string, unknown>;
|
|
172
|
+
phase: HarnessPhase;
|
|
173
|
+
aborted: boolean;
|
|
174
|
+
budgetBypass: boolean;
|
|
175
|
+
agentKind: string;
|
|
176
|
+
}): { blocked: boolean; reason?: string } {
|
|
177
|
+
if (!args.sessionActive) return { blocked: false, reason: "" };
|
|
178
|
+
return evaluateContextModeMutation(
|
|
179
|
+
args.toolName,
|
|
180
|
+
args.toolInput,
|
|
181
|
+
args.phase,
|
|
182
|
+
{
|
|
183
|
+
aborted: args.aborted,
|
|
184
|
+
budgetBypass: args.budgetBypass,
|
|
185
|
+
readOnlyAgent: isReadOnlyAgentKind(args.agentKind),
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function shouldBlockEvalPlanPacketWrite(args: {
|
|
191
|
+
sessionActive: boolean;
|
|
192
|
+
runCtx: HarnessRunContext | null;
|
|
193
|
+
phase: HarnessPhase;
|
|
194
|
+
toolName: string;
|
|
195
|
+
toolInput: Record<string, unknown>;
|
|
196
|
+
}): boolean {
|
|
197
|
+
if (!args.sessionActive || !args.runCtx?.plan_packet_path) return false;
|
|
198
|
+
if (args.phase !== "evaluate" && args.phase !== "adversary") return false;
|
|
199
|
+
if (args.toolName !== "write" && args.toolName !== "edit") return false;
|
|
200
|
+
const target = String(args.toolInput.path ?? args.toolInput.filePath ?? "");
|
|
201
|
+
return target.includes("plan-packet.yaml");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveMutatingBashFlags(args: {
|
|
205
|
+
sessionActive: boolean;
|
|
206
|
+
bashCommand: string;
|
|
207
|
+
aborted: boolean;
|
|
208
|
+
phase: HarnessPhase;
|
|
209
|
+
toolName: string;
|
|
210
|
+
}): { mutatingBashPhaseBlock: boolean; abortMutatingBlock: boolean } {
|
|
211
|
+
const isMutating = Boolean(
|
|
212
|
+
args.bashCommand && isMutatingBash(args.bashCommand),
|
|
213
|
+
);
|
|
214
|
+
return {
|
|
215
|
+
mutatingBashPhaseBlock:
|
|
216
|
+
args.sessionActive &&
|
|
217
|
+
isMutating &&
|
|
218
|
+
!args.aborted &&
|
|
219
|
+
args.phase !== "execute" &&
|
|
220
|
+
args.phase !== "merge",
|
|
221
|
+
abortMutatingBlock:
|
|
222
|
+
args.sessionActive &&
|
|
223
|
+
args.aborted &&
|
|
224
|
+
(isMutating || args.toolName === "write" || args.toolName === "edit"),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
128
228
|
function harnessSessionActive(entries: unknown[]): boolean {
|
|
129
229
|
return isHarnessAutoSession(entries);
|
|
130
230
|
}
|
|
@@ -142,35 +242,27 @@ export async function buildHarnessAgtEvaluationContext(
|
|
|
142
242
|
input.toolName === "bash" ? String(input.toolInput.command ?? "") : "";
|
|
143
243
|
const sessionActive = harnessSessionActive(input.entries);
|
|
144
244
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
entries: input.entries,
|
|
157
|
-
ownerSessionId: runCtx?.owner_pi_session_id,
|
|
158
|
-
currentSessionId: input.sessionId,
|
|
159
|
-
},
|
|
160
|
-
)
|
|
161
|
-
: { allowed: true };
|
|
245
|
+
const planMutation = await resolvePlanMutation({
|
|
246
|
+
sessionActive,
|
|
247
|
+
toolName: input.toolName,
|
|
248
|
+
toolInput: input.toolInput,
|
|
249
|
+
phase,
|
|
250
|
+
runCtx,
|
|
251
|
+
projectRoot: input.projectRoot,
|
|
252
|
+
aborted: input.policyState.aborted,
|
|
253
|
+
entries: input.entries,
|
|
254
|
+
sessionId: input.sessionId,
|
|
255
|
+
});
|
|
162
256
|
|
|
163
|
-
const ctxMode =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
173
|
-
: { blocked: false, reason: "" };
|
|
257
|
+
const ctxMode = resolveContextMode({
|
|
258
|
+
sessionActive,
|
|
259
|
+
toolName: input.toolName,
|
|
260
|
+
toolInput: input.toolInput,
|
|
261
|
+
phase,
|
|
262
|
+
aborted: input.policyState.aborted,
|
|
263
|
+
budgetBypass: input.policyState.budgetBypass,
|
|
264
|
+
agentKind,
|
|
265
|
+
});
|
|
174
266
|
|
|
175
267
|
const spawnDecision = evaluateSubagentToolCall(input.toolName, agentId);
|
|
176
268
|
|
|
@@ -184,40 +276,27 @@ export async function buildHarnessAgtEvaluationContext(
|
|
|
184
276
|
isParentOrchestrator,
|
|
185
277
|
});
|
|
186
278
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
const target = String(
|
|
195
|
-
input.toolInput.path ?? input.toolInput.filePath ?? "",
|
|
196
|
-
);
|
|
197
|
-
if (target.includes("plan-packet.yaml")) {
|
|
198
|
-
evalPlanPacketBlock = true;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
279
|
+
const evalPlanPacketBlock = shouldBlockEvalPlanPacketWrite({
|
|
280
|
+
sessionActive,
|
|
281
|
+
runCtx,
|
|
282
|
+
phase,
|
|
283
|
+
toolName: input.toolName,
|
|
284
|
+
toolInput: input.toolInput,
|
|
285
|
+
});
|
|
201
286
|
|
|
202
287
|
const writePath =
|
|
203
288
|
input.toolName === "write" || input.toolName === "edit"
|
|
204
289
|
? extractWritePathFromToolInput(input.toolInput)
|
|
205
290
|
: null;
|
|
206
291
|
|
|
207
|
-
const mutatingBashPhaseBlock =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
sessionActive &&
|
|
216
|
-
input.policyState.aborted &&
|
|
217
|
-
((bashCommand && isMutatingBash(bashCommand)) ||
|
|
218
|
-
input.toolName === "write" ||
|
|
219
|
-
input.toolName === "edit");
|
|
220
|
-
|
|
292
|
+
const { mutatingBashPhaseBlock, abortMutatingBlock } =
|
|
293
|
+
resolveMutatingBashFlags({
|
|
294
|
+
sessionActive,
|
|
295
|
+
bashCommand,
|
|
296
|
+
aborted: input.policyState.aborted,
|
|
297
|
+
phase,
|
|
298
|
+
toolName: input.toolName,
|
|
299
|
+
});
|
|
221
300
|
return {
|
|
222
301
|
tool_name: input.toolName,
|
|
223
302
|
harness_phase: phase,
|
|
@@ -249,14 +328,7 @@ export async function buildHarnessAgtEvaluationContext(
|
|
|
249
328
|
eval_plan_packet_write_block: evalPlanPacketBlock,
|
|
250
329
|
is_submit_tool: input.toolName.startsWith("submit_"),
|
|
251
330
|
is_planning_agent: isHarnessPlanningAgent(agentId),
|
|
252
|
-
is_read_only_kind:
|
|
253
|
-
agentKind === "planner" ||
|
|
254
|
-
agentKind === "evaluator" ||
|
|
255
|
-
agentKind === "adversary" ||
|
|
256
|
-
agentKind === "tie_breaker" ||
|
|
257
|
-
agentKind === "trace" ||
|
|
258
|
-
agentKind === "incident" ||
|
|
259
|
-
agentKind === "meta",
|
|
331
|
+
is_read_only_kind: isReadOnlyAgentKind(agentKind),
|
|
260
332
|
is_executor_kind: agentKind === "executor",
|
|
261
333
|
trust_score: Number(process.env.HARNESS_TRUST_SCORE ?? "1"),
|
|
262
334
|
delegation_ceiling: Number(process.env.HARNESS_DELEGATION_CEILING ?? "1"),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AskResponse, QuestionnaireDetail } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export function parseGlimpseRawResult(
|
|
4
|
+
raw: Record<string, unknown> | null,
|
|
5
|
+
cancelled: boolean,
|
|
6
|
+
): AskResponse | null {
|
|
7
|
+
if (cancelled || !raw) return null;
|
|
8
|
+
|
|
9
|
+
const kind = raw.kind;
|
|
10
|
+
if (kind === "freeform") {
|
|
11
|
+
return {
|
|
12
|
+
kind: "freeform",
|
|
13
|
+
text: String(raw.text ?? "").trim(),
|
|
14
|
+
additionalComments: pickString(raw.additionalComments),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (kind === "questionnaire") {
|
|
19
|
+
const questionnaireDetails: QuestionnaireDetail[] = Array.isArray(
|
|
20
|
+
raw.questionnaireDetails,
|
|
21
|
+
)
|
|
22
|
+
? raw.questionnaireDetails.map((d: unknown) => {
|
|
23
|
+
const entry = d as Record<string, unknown>;
|
|
24
|
+
return {
|
|
25
|
+
question: String(entry.question ?? ""),
|
|
26
|
+
answer: String(entry.answer ?? ""),
|
|
27
|
+
kind: entry.kind === "freeform" ? "freeform" : "selection",
|
|
28
|
+
comment: pickString(entry.comment),
|
|
29
|
+
};
|
|
30
|
+
})
|
|
31
|
+
: [];
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
kind: "questionnaire",
|
|
35
|
+
questionnaireDetails,
|
|
36
|
+
additionalComments: pickString(raw.additionalComments),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const selections = Array.isArray(raw.selections)
|
|
41
|
+
? raw.selections.map(String)
|
|
42
|
+
: raw.selection
|
|
43
|
+
? [String(raw.selection)]
|
|
44
|
+
: [];
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
kind: "selection",
|
|
48
|
+
selections,
|
|
49
|
+
comment: pickString(raw.comment),
|
|
50
|
+
additionalComments: pickString(raw.additionalComments),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pickString(raw: unknown): string | undefined {
|
|
55
|
+
return raw ? String(raw) : undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ValidatedAskParams } from "../types.js";
|
|
2
|
+
import type { GlimpseAskUserPayload } from "./glimpse-payload.js";
|
|
3
|
+
|
|
4
|
+
export function buildGlimpsePayload(
|
|
5
|
+
validated: ValidatedAskParams,
|
|
6
|
+
sessionName?: string,
|
|
7
|
+
): GlimpseAskUserPayload {
|
|
8
|
+
const hasQuestions = validated.mode === "questionnaire";
|
|
9
|
+
const hasOptions = validated.options.length > 0;
|
|
10
|
+
|
|
11
|
+
let payloadType: GlimpseAskUserPayload["type"];
|
|
12
|
+
if (hasQuestions) {
|
|
13
|
+
payloadType = "questionnaire";
|
|
14
|
+
} else if (!hasOptions) {
|
|
15
|
+
payloadType = "freeform";
|
|
16
|
+
} else if (validated.allowMultiple) {
|
|
17
|
+
payloadType = "multi-select";
|
|
18
|
+
} else {
|
|
19
|
+
payloadType = "single-select";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let question = validated.question;
|
|
23
|
+
let context = validated.context;
|
|
24
|
+
if (!context && validated.question.length > 120) {
|
|
25
|
+
const match = validated.question.match(/^(.+?[.?!])(\s+|$)/);
|
|
26
|
+
if (match && match[0].length < validated.question.length) {
|
|
27
|
+
question = match[1].trim();
|
|
28
|
+
context = validated.question.slice(match[0].length).trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
type: payloadType,
|
|
34
|
+
question,
|
|
35
|
+
context,
|
|
36
|
+
contextFormat: validated.contextFormat,
|
|
37
|
+
options: validated.options.map((o) => ({
|
|
38
|
+
title: o.title,
|
|
39
|
+
description: o.description,
|
|
40
|
+
recommended: o.recommended,
|
|
41
|
+
})),
|
|
42
|
+
questions: validated.questions.map((q) => ({
|
|
43
|
+
title: q.title,
|
|
44
|
+
description: q.description,
|
|
45
|
+
allowMultiple: q.allowMultiple,
|
|
46
|
+
options: q.options.map((o) => ({
|
|
47
|
+
title: o.title,
|
|
48
|
+
description: o.description,
|
|
49
|
+
recommended: o.recommended,
|
|
50
|
+
})),
|
|
51
|
+
})),
|
|
52
|
+
allowMultiple: validated.allowMultiple,
|
|
53
|
+
allowFreeform: validated.allowFreeform,
|
|
54
|
+
allowComment: validated.allowComment,
|
|
55
|
+
allowSkip: validated.allowSkip,
|
|
56
|
+
sessionName,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synced with @alexleekt/pi-ask-user-glimpse@0.5.1 shared/ask-user.ts — harness-owned payload contract.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface GlimpseQuestionOption {
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
recommended?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GlimpseQuestion {
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
options?: GlimpseQuestionOption[];
|
|
15
|
+
allowMultiple?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type GlimpsePayloadType =
|
|
19
|
+
| "single-select"
|
|
20
|
+
| "multi-select"
|
|
21
|
+
| "questionnaire"
|
|
22
|
+
| "freeform";
|
|
23
|
+
|
|
24
|
+
export interface GlimpseAskUserPayload {
|
|
25
|
+
type: GlimpsePayloadType;
|
|
26
|
+
question: string;
|
|
27
|
+
context?: string;
|
|
28
|
+
contextFormat?: "markdown" | "html";
|
|
29
|
+
options: GlimpseQuestionOption[];
|
|
30
|
+
questions?: GlimpseQuestion[];
|
|
31
|
+
allowMultiple: boolean;
|
|
32
|
+
allowFreeform: boolean;
|
|
33
|
+
allowComment: boolean;
|
|
34
|
+
allowSkip?: boolean;
|
|
35
|
+
sessionName?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const GLIMPSE_FREEFORM_OPTION_TITLE = "My answer isn't listed above";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DialogResult,
|
|
3
|
+
NormalizedQuestion,
|
|
4
|
+
QuestionnaireDetail,
|
|
5
|
+
ValidatedAskParams,
|
|
6
|
+
} from "../types.js";
|
|
7
|
+
|
|
8
|
+
/** Build flat ask params for one questionnaire card (TUI/headless sequential). */
|
|
9
|
+
export function questionToFlatParams(
|
|
10
|
+
parent: ValidatedAskParams,
|
|
11
|
+
q: NormalizedQuestion,
|
|
12
|
+
index: number,
|
|
13
|
+
total: number,
|
|
14
|
+
): ValidatedAskParams {
|
|
15
|
+
const header =
|
|
16
|
+
total > 1 ? `[${index + 1}/${total}] ${parent.question}` : parent.question;
|
|
17
|
+
return {
|
|
18
|
+
...parent,
|
|
19
|
+
mode: "flat",
|
|
20
|
+
question: q.title,
|
|
21
|
+
context: [header, q.description].filter(Boolean).join("\n\n") || undefined,
|
|
22
|
+
options: q.options,
|
|
23
|
+
questions: [],
|
|
24
|
+
allowMultiple: q.allowMultiple,
|
|
25
|
+
allowFreeform: q.options.length === 0 ? true : parent.allowFreeform,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mergeQuestionnaireResults(
|
|
30
|
+
details: QuestionnaireDetail[],
|
|
31
|
+
last?: DialogResult,
|
|
32
|
+
): DialogResult {
|
|
33
|
+
const additionalComments =
|
|
34
|
+
last?.response?.kind === "freeform"
|
|
35
|
+
? last.response.text
|
|
36
|
+
: last?.response && "additionalComments" in last.response
|
|
37
|
+
? last.response.additionalComments
|
|
38
|
+
: undefined;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
response: {
|
|
42
|
+
kind: "questionnaire",
|
|
43
|
+
questionnaireDetails: details,
|
|
44
|
+
additionalComments,
|
|
45
|
+
},
|
|
46
|
+
cancelled: false,
|
|
47
|
+
ui_backend: last?.ui_backend ?? "tui",
|
|
48
|
+
ui_degraded: last?.ui_degraded,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function detailFromFlatResult(
|
|
53
|
+
questionLabel: string,
|
|
54
|
+
result: DialogResult,
|
|
55
|
+
): QuestionnaireDetail | null {
|
|
56
|
+
if (result.cancelled || !result.response) return null;
|
|
57
|
+
const r = result.response;
|
|
58
|
+
if (r.kind === "freeform") {
|
|
59
|
+
return {
|
|
60
|
+
question: questionLabel,
|
|
61
|
+
answer: r.text,
|
|
62
|
+
kind: "freeform",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (r.kind === "selection") {
|
|
66
|
+
return {
|
|
67
|
+
question: questionLabel,
|
|
68
|
+
answer: r.selections.join(", "),
|
|
69
|
+
kind: "selection",
|
|
70
|
+
comment: r.comment,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|