pi-harness-runtime 1.1.321 → 1.1.323
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/RULES.md +19 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/dist/extension.d.ts.map +1 -1
- package/packages/agent-policy/dist/extension.js +47 -12
- package/packages/agent-policy/dist/extension.js.map +1 -1
- package/packages/agent-policy/dist/index.d.ts +1 -1
- package/packages/agent-policy/dist/index.d.ts.map +1 -1
- package/packages/agent-policy/dist/index.js +1 -1
- package/packages/agent-policy/dist/index.js.map +1 -1
- package/packages/agent-policy/dist/policy-renderer.d.ts +15 -0
- package/packages/agent-policy/dist/policy-renderer.d.ts.map +1 -1
- package/packages/agent-policy/dist/policy-renderer.js +59 -0
- package/packages/agent-policy/dist/policy-renderer.js.map +1 -1
- package/packages/agent-policy/dist/ssh-guard.d.ts +51 -0
- package/packages/agent-policy/dist/ssh-guard.d.ts.map +1 -0
- package/packages/agent-policy/dist/ssh-guard.js +172 -0
- package/packages/agent-policy/dist/ssh-guard.js.map +1 -0
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/src/extension.ts +53 -15
- package/packages/agent-policy/src/index.ts +2 -0
- package/packages/agent-policy/src/policy-renderer.ts +73 -0
- package/packages/agent-policy/src/ssh-guard.ts +204 -0
- package/packages/agent-policy/test/policy-retention.test.ts +40 -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/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- 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-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/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/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/webmcp-tester/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
|
@@ -26,8 +26,11 @@ import { getPolicyStore } from "./policy-store.js";
|
|
|
26
26
|
import {
|
|
27
27
|
renderSystemContract,
|
|
28
28
|
renderFullRules,
|
|
29
|
+
renderFullPolicyText,
|
|
30
|
+
checkPolicyTextSize,
|
|
29
31
|
} from "./policy-renderer.js";
|
|
30
32
|
import { classifyToolCall } from "./types.js";
|
|
33
|
+
import { checkSshNecessity } from "./ssh-guard.js";
|
|
31
34
|
import { randomUUID } from "node:crypto";
|
|
32
35
|
|
|
33
36
|
// ---------------------------------------------------------------------------
|
|
@@ -78,11 +81,26 @@ export function registerAgentPolicy(
|
|
|
78
81
|
"system_prompt"
|
|
79
82
|
);
|
|
80
83
|
|
|
81
|
-
//
|
|
82
|
-
|
|
84
|
+
// Decide what to deliver based on compaction state
|
|
85
|
+
let policyText: string;
|
|
86
|
+
if (needsFullDelivery) {
|
|
87
|
+
// After compaction, deliver FULL policy text so model has actual rules
|
|
88
|
+
const sizeCheck = checkPolicyTextSize(manifest);
|
|
89
|
+
if (sizeCheck.fits) {
|
|
90
|
+
policyText = renderFullPolicyText(manifest);
|
|
91
|
+
} else {
|
|
92
|
+
// Too large, deliver contract with warning
|
|
93
|
+
policyText = renderSystemContract(manifest) +
|
|
94
|
+
`\n[WARNING: ${sizeCheck.reason}]`;
|
|
95
|
+
}
|
|
96
|
+
needsFullDelivery = false;
|
|
97
|
+
} else {
|
|
98
|
+
// Normal delivery: compact contract
|
|
99
|
+
policyText = renderSystemContract(manifest);
|
|
100
|
+
}
|
|
83
101
|
|
|
84
102
|
// Build augmented system prompt
|
|
85
|
-
const augmentedPrompt = event.systemPrompt + "\n\n" +
|
|
103
|
+
const augmentedPrompt = event.systemPrompt + "\n\n" + policyText;
|
|
86
104
|
|
|
87
105
|
// Log for debugging
|
|
88
106
|
logInfo("[agent-policy]", `Delivered policy ${manifest.revision} to session ${receipt.sessionId}`);
|
|
@@ -155,12 +173,30 @@ Use harness_rules to check policy status.
|
|
|
155
173
|
}
|
|
156
174
|
}
|
|
157
175
|
|
|
176
|
+
// Check SSH guard for shell/bash commands
|
|
177
|
+
if ((toolName === "bash" || toolName === "shell" || toolName === "ctx_execute") && input && typeof input === "object") {
|
|
178
|
+
const inputObj = input as Record<string, unknown>;
|
|
179
|
+
const command = String(inputObj.command ?? "");
|
|
180
|
+
|
|
181
|
+
// Check if this is an SSH command
|
|
182
|
+
if (/\bssh\b/.test(command)) {
|
|
183
|
+
const sshCheck = await checkSshNecessity(command);
|
|
184
|
+
if (!sshCheck.allowed) {
|
|
185
|
+
logInfo("[agent-policy]", `Blocked SSH to local destination: ${sshCheck.message}`);
|
|
186
|
+
return {
|
|
187
|
+
block: true,
|
|
188
|
+
reason: `[SSH GUARD] ${sshCheck.message}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
158
194
|
// Policy satisfied, allow the call
|
|
159
195
|
return {};
|
|
160
196
|
} catch (err) {
|
|
161
197
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
162
198
|
logError("[agent-policy]", `Policy check failed: ${errorMessage}`);
|
|
163
|
-
|
|
199
|
+
|
|
164
200
|
// On error, fail open for the command but log the issue
|
|
165
201
|
// This matches pi-lens's total handler guard behavior
|
|
166
202
|
return {};
|
|
@@ -336,24 +372,26 @@ Use harness_rules to check policy status.
|
|
|
336
372
|
logInfo("[agent-policy]", `Session started: ${sessionId} in ${cwd}`);
|
|
337
373
|
});
|
|
338
374
|
|
|
375
|
+
// Track if we need full policy delivery (after compaction)
|
|
376
|
+
let needsFullDelivery = false;
|
|
377
|
+
|
|
339
378
|
// ---------------------------------------------------------------------------
|
|
340
|
-
//
|
|
341
|
-
// ---------------------------------------------------------------------------
|
|
342
|
-
// session_compact: Invalidate delivery coverage for next request
|
|
379
|
+
// session_compact: Flag for full policy delivery
|
|
343
380
|
// ---------------------------------------------------------------------------
|
|
344
381
|
//
|
|
345
|
-
// After compaction,
|
|
346
|
-
//
|
|
382
|
+
// After compaction, we must deliver FULL policy text on next before_agent_start.
|
|
383
|
+
// The model needs actual rule content, not a reminder pointer.
|
|
347
384
|
//
|
|
348
|
-
|
|
349
385
|
pi.on("session_compact", (_event: SessionCompactEvent, ctx: unknown) => {
|
|
350
386
|
const sessionId = getSessionIdFromCtx(ctx, fallbackSessionId);
|
|
351
|
-
|
|
352
|
-
// Clear receipts
|
|
353
|
-
// This ensures rules are re-delivered after compaction without stale receipt blocking
|
|
387
|
+
|
|
388
|
+
// Clear receipts so before_agent_start will re-deliver
|
|
354
389
|
store.clearSession(sessionId);
|
|
355
|
-
|
|
356
|
-
|
|
390
|
+
|
|
391
|
+
// Flag that next before_agent_start should deliver full text
|
|
392
|
+
needsFullDelivery = true;
|
|
393
|
+
|
|
394
|
+
logInfo("[agent-policy]", `Session compact: flagged for full policy delivery to session ${sessionId}`);
|
|
357
395
|
});
|
|
358
396
|
|
|
359
397
|
// ---------------------------------------------------------------------------
|
|
@@ -139,6 +139,79 @@ export function parseSections(content: string, source: string, scope: string, pr
|
|
|
139
139
|
return sections;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Full Policy Text Rendering
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Render full policy text for injection after compaction.
|
|
148
|
+
* This includes the ACTUAL rule file contents, not just a contract pointer.
|
|
149
|
+
* Used after session_compact to deliver fresh policy text to the model.
|
|
150
|
+
*/
|
|
151
|
+
export function renderFullPolicyText(manifest: PolicyManifest): string {
|
|
152
|
+
const lines: string[] = [];
|
|
153
|
+
|
|
154
|
+
lines.push(`PI-HARNESS POLICY (full text) revision=${manifest.revision} coverage=${manifest.coverage}`);
|
|
155
|
+
lines.push("");
|
|
156
|
+
|
|
157
|
+
// Include all mandatory sources with their full content
|
|
158
|
+
for (const source of manifest.sources) {
|
|
159
|
+
if (source.priority === "mandatory" || source.harnessOwned) {
|
|
160
|
+
const content = readSourceContent(source.path);
|
|
161
|
+
if (content) {
|
|
162
|
+
lines.push(`--- ${source.path} [${source.priority}] ---`);
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push(content);
|
|
165
|
+
lines.push("");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Include advisory sources if they exist
|
|
171
|
+
for (const source of manifest.sources) {
|
|
172
|
+
if (source.priority === "advisory" && !source.harnessOwned) {
|
|
173
|
+
const content = readSourceContent(source.path);
|
|
174
|
+
if (content) {
|
|
175
|
+
lines.push(`--- ${source.path} [advisory] ---`);
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push(content);
|
|
178
|
+
lines.push("");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return lines.join("\n");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Check if full policy text would fit in context.
|
|
188
|
+
* Returns { fits: true } or { fits: false, reason: string, bytes: number }.
|
|
189
|
+
*/
|
|
190
|
+
export function checkPolicyTextSize(manifest: PolicyManifest, maxBytes = 100 * 1024): {
|
|
191
|
+
fits: boolean;
|
|
192
|
+
bytes?: number;
|
|
193
|
+
reason?: string;
|
|
194
|
+
} {
|
|
195
|
+
let totalBytes = 0;
|
|
196
|
+
|
|
197
|
+
for (const source of manifest.sources) {
|
|
198
|
+
const content = readSourceContent(source.path);
|
|
199
|
+
if (content) {
|
|
200
|
+
totalBytes += Buffer.byteLength(content, "utf8");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (totalBytes <= maxBytes) {
|
|
205
|
+
return { fits: true, bytes: totalBytes };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
fits: false,
|
|
210
|
+
bytes: totalBytes,
|
|
211
|
+
reason: `Full policy text (${totalBytes} bytes) exceeds limit (${maxBytes} bytes). Use harness_rules read for selective retrieval.`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
142
215
|
// ---------------------------------------------------------------------------
|
|
143
216
|
// Full Rules Rendering
|
|
144
217
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH Guard Tool
|
|
3
|
+
*
|
|
4
|
+
* Intercepts SSH commands and checks if the destination matches the local environment.
|
|
5
|
+
* Combines automatic checking with a fresh reminder when the agent proposes SSH.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import dns from "node:dns";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
|
|
13
|
+
const lookupAsync = promisify(dns.lookup);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result of SSH guard check
|
|
17
|
+
*/
|
|
18
|
+
export interface SshGuardResult {
|
|
19
|
+
/** Whether SSH is allowed to proceed */
|
|
20
|
+
allowed: boolean;
|
|
21
|
+
/** Message explaining the decision */
|
|
22
|
+
message: string;
|
|
23
|
+
/** Whether the destination matches local */
|
|
24
|
+
isLocal: boolean;
|
|
25
|
+
/** Resolved destination address */
|
|
26
|
+
destinationAddress?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse SSH destination from command line
|
|
31
|
+
*/
|
|
32
|
+
function parseSshDestination(command: string): string | null {
|
|
33
|
+
// Match patterns like:
|
|
34
|
+
// ssh user@host
|
|
35
|
+
// ssh host
|
|
36
|
+
// ssh -p port user@host
|
|
37
|
+
// ssh host.domain.com
|
|
38
|
+
const match = command.match(/ssh\s+(?:-[a-z]+\s+)*((?:[a-zA-Z0-9_-]+\@)?[a-zA-Z0-9._-]+)/);
|
|
39
|
+
return match ? match[1] : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get local IP addresses (cross-platform)
|
|
44
|
+
*/
|
|
45
|
+
function getLocalAddresses(): string[] {
|
|
46
|
+
try {
|
|
47
|
+
const isWindows = process.platform === "win32";
|
|
48
|
+
let output: string;
|
|
49
|
+
|
|
50
|
+
if (isWindows) {
|
|
51
|
+
// Windows: use ipconfig
|
|
52
|
+
output = execSync("ipconfig", { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
53
|
+
} else {
|
|
54
|
+
// Unix-like: use ip address
|
|
55
|
+
output = execSync("ip -j address 2>/dev/null || ip address 2>/dev/null", {
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
timeout: 5000,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const addresses: string[] = ["127.0.0.1", "::1", "localhost"];
|
|
62
|
+
|
|
63
|
+
// Parse JSON output if available (Linux)
|
|
64
|
+
try {
|
|
65
|
+
const json = JSON.parse(output);
|
|
66
|
+
for (const iface of json) {
|
|
67
|
+
for (const addr of iface.addr_info || []) {
|
|
68
|
+
if (addr.local !== addr.peer) {
|
|
69
|
+
addresses.push(addr.local);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Fallback to text parsing
|
|
75
|
+
if (isWindows) {
|
|
76
|
+
// Windows ipconfig: parse "IPv4 Address. . . . . . . . . . : X.X.X.X"
|
|
77
|
+
const windowsMatch = output.matchAll(/IPv4[^:]*:\s*([0-9.]+)/gi);
|
|
78
|
+
for (const match of windowsMatch) {
|
|
79
|
+
const ip = match[1];
|
|
80
|
+
if (ip !== "127.0.0.1") {
|
|
81
|
+
addresses.push(ip);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Also check for IPv6
|
|
85
|
+
const ipv6Match = output.matchAll(/IPv6[^:]*:\s*([0-9a-f:]+)/gi);
|
|
86
|
+
for (const match of ipv6Match) {
|
|
87
|
+
addresses.push(match[1]);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
// Unix-like: parse "inet X.X.X.X"
|
|
91
|
+
const ipMatch = output.match(/inet\s+([0-9.]+)/g);
|
|
92
|
+
if (ipMatch) {
|
|
93
|
+
for (const match of ipMatch) {
|
|
94
|
+
const ip = match.replace("inet ", "");
|
|
95
|
+
if (ip !== "127.0.0.1") {
|
|
96
|
+
addresses.push(ip);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return [...new Set(addresses)];
|
|
104
|
+
} catch {
|
|
105
|
+
return ["127.0.0.1", "::1", "localhost"];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve hostname to IP address
|
|
111
|
+
*/
|
|
112
|
+
async function resolveHost(hostname: string): Promise<string | null> {
|
|
113
|
+
// Skip if already an IP
|
|
114
|
+
if (/^[0-9.]+$/.test(hostname)) return hostname;
|
|
115
|
+
if (/^[0-9a-f:]+$/i.test(hostname)) return hostname;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const result = await lookupAsync(hostname);
|
|
119
|
+
return result.address;
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if destination is local
|
|
127
|
+
*/
|
|
128
|
+
export async function checkSshNecessity(command: string): Promise<SshGuardResult> {
|
|
129
|
+
const destination = parseSshDestination(command);
|
|
130
|
+
|
|
131
|
+
if (!destination) {
|
|
132
|
+
return {
|
|
133
|
+
allowed: true,
|
|
134
|
+
message: "Could not parse SSH destination",
|
|
135
|
+
isLocal: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const localAddresses = getLocalAddresses();
|
|
140
|
+
const resolved = await resolveHost(destination);
|
|
141
|
+
|
|
142
|
+
if (!resolved) {
|
|
143
|
+
return {
|
|
144
|
+
allowed: true,
|
|
145
|
+
message: `Could not resolve SSH destination: ${destination}`,
|
|
146
|
+
isLocal: false,
|
|
147
|
+
destinationAddress: undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const isLocal = localAddresses.some(
|
|
152
|
+
(addr) => addr === resolved || resolved === addr
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (isLocal) {
|
|
156
|
+
return {
|
|
157
|
+
allowed: false,
|
|
158
|
+
message: `SSH destination ${destination} (${resolved}) matches this environment (local addresses: ${localAddresses.join(", ")}). Run the command locally, or explain why a separate SSH session is required.`,
|
|
159
|
+
isLocal: true,
|
|
160
|
+
destinationAddress: resolved,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
allowed: true,
|
|
166
|
+
message: `SSH to ${destination} (${resolved}) appears necessary - destination differs from local addresses`,
|
|
167
|
+
isLocal: false,
|
|
168
|
+
destinationAddress: resolved,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Tool definition for SSH guard
|
|
174
|
+
*/
|
|
175
|
+
export const SshGuardTool = {
|
|
176
|
+
name: "ssh_guard_check",
|
|
177
|
+
description: "Check if SSH destination matches local environment before allowing SSH",
|
|
178
|
+
parameters: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
command: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "The SSH command to check",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
required: ["command"],
|
|
187
|
+
},
|
|
188
|
+
} as const;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Execute SSH guard check
|
|
192
|
+
*/
|
|
193
|
+
export async function executeSshGuard(
|
|
194
|
+
args: { command: string },
|
|
195
|
+
_api: unknown,
|
|
196
|
+
_context: unknown
|
|
197
|
+
): Promise<{ allowed: boolean; message: string; isLocal: boolean }> {
|
|
198
|
+
const result = await checkSshNecessity(args.command);
|
|
199
|
+
return {
|
|
200
|
+
allowed: result.allowed,
|
|
201
|
+
message: result.message,
|
|
202
|
+
isLocal: result.isLocal,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -292,8 +292,47 @@ describe("Policy Retention Tests", () => {
|
|
|
292
292
|
|
|
293
293
|
// Note partial delivery (only section 1)
|
|
294
294
|
const receipt = store.noteDelivered(sessionId, manifest, "tool", ["section-1"]);
|
|
295
|
-
|
|
296
295
|
assert.deepStrictEqual(receipt.sectionsDelivered, ["section-1"]);
|
|
297
296
|
});
|
|
298
297
|
});
|
|
298
|
+
|
|
299
|
+
// --- Full Policy Text Delivery Tests ---
|
|
300
|
+
describe("Full policy text delivery", () => {
|
|
301
|
+
it("should compute full policy text after revision change", async () => {
|
|
302
|
+
writeFileSync(AGENTS_MD, "# Full Agents\n\n## Rule 1\n\nContent 1.\n\n## Rule 2\n\nContent 2.", "utf8");
|
|
303
|
+
|
|
304
|
+
const loader = new PolicyLoader({
|
|
305
|
+
harnessRulesPath: new URL(`file://${AGENTS_MD}`),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const manifest = await loader.load(TEST_DIR);
|
|
309
|
+
|
|
310
|
+
// Full text should include actual content, not just contract pointers
|
|
311
|
+
const { renderFullPolicyText } = await import("../dist/policy-renderer.js");
|
|
312
|
+
const fullText = renderFullPolicyText(manifest);
|
|
313
|
+
|
|
314
|
+
assert.ok(fullText.includes("Full Agents"), "Should include source title");
|
|
315
|
+
assert.ok(fullText.includes("Rule 1"), "Should include section heading");
|
|
316
|
+
assert.ok(fullText.includes("Content 1"), "Should include actual content");
|
|
317
|
+
assert.ok(fullText.includes("revision="), "Should include revision info");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should detect when full policy exceeds size limit", async () => {
|
|
321
|
+
const loader = new PolicyLoader({
|
|
322
|
+
harnessRulesPath: new URL(`file://${AGENTS_MD}`),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const manifest = await loader.load(TEST_DIR);
|
|
326
|
+
const { checkPolicyTextSize } = await import("../dist/policy-renderer.js");
|
|
327
|
+
|
|
328
|
+
// With normal-sized content, should fit in 100KB limit
|
|
329
|
+
const result = checkPolicyTextSize(manifest, 100 * 1024);
|
|
330
|
+
assert.strictEqual(result.fits, true, "Normal policy should fit in 100KB");
|
|
331
|
+
|
|
332
|
+
// With very small limit, should report overflow
|
|
333
|
+
const smallResult = checkPolicyTextSize(manifest, 10);
|
|
334
|
+
assert.strictEqual(smallResult.fits, false, "Should report when limit is too small");
|
|
335
|
+
assert.ok(smallResult.reason?.includes("exceeds limit"), "Should explain overflow");
|
|
336
|
+
});
|
|
337
|
+
});
|
|
299
338
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.323",
|
|
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",
|