@chainingintention/pi-web-cn 1.202608.7 → 1.202608.9
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/dist/client/assets/{CodeViewer-CWKEeX0r.js → CodeViewer-B9BLGnQ2.js} +1 -1
- package/dist/client/assets/{TerminalPanel-BmA7Ayzk.js → TerminalPanel-C1BWg5W2.js} +1 -1
- package/dist/client/assets/{UnifiedDiffViewer-2DACwxFY.js → UnifiedDiffViewer-ClTQ6Ok0.js} +1 -1
- package/dist/client/assets/{index-D6j67Izj.js → index-x7EWN9ro.js} +124 -124
- package/dist/client/index.html +1 -1
- package/dist/server/audit/managementAuditStore.js +4 -2
- package/dist/server/audit/managementAuditStore.js.map +1 -1
- package/dist/server/managementEmbed.js +6 -0
- package/dist/server/managementEmbed.js.map +1 -1
- package/dist/server/sessions/managementAgentTools.js +12 -4
- package/dist/server/sessions/managementAgentTools.js.map +1 -1
- package/dist/server/sessions/managementSandbox.js +5 -5
- package/dist/server/sessions/managementSandbox.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +156 -38
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/workbench/skillSync.js +9 -1
- package/dist/server/workbench/skillSync.js.map +1 -1
- package/docs/config.md +1 -1
- package/package.json +2 -1
- package/skills/README.md +12 -0
- package/skills/relay/SKILL.md +115 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { realpathSync, statSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { constants, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, getAgentDir, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, ModelRuntime, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import { projectBrowserMessage } from "../browserMessageProjection.js";
|
|
6
7
|
import { pageMessagesAtSafeBoundary } from "./messagePaging.js";
|
|
@@ -61,6 +62,25 @@ const STARTUP_PHASE_EXTENSIONS = "正在加载会话扩展";
|
|
|
61
62
|
const STARTUP_CONCURRENT_CATALOG_REFRESH = "服务商模型列表正在刷新";
|
|
62
63
|
const MAX_UNREAD_PUBLICATION_RETRY_MS = 30_000;
|
|
63
64
|
const MAX_PENDING_UNREAD_MUTATIONS = SESSION_UNREAD_LIMIT + 1;
|
|
65
|
+
function auditVisibleAssistantMessage(message) {
|
|
66
|
+
if (!isAuditRecord(message))
|
|
67
|
+
return message;
|
|
68
|
+
const record = message;
|
|
69
|
+
const content = record["content"];
|
|
70
|
+
if (!Array.isArray(content))
|
|
71
|
+
return message;
|
|
72
|
+
return {
|
|
73
|
+
...record,
|
|
74
|
+
content: content.filter(part => (typeof part !== "object"
|
|
75
|
+
|| part === null
|
|
76
|
+
|| Array.isArray(part)
|
|
77
|
+
|| !isAuditRecord(part)
|
|
78
|
+
|| part["type"] !== "thinking")),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function isAuditRecord(value) {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
+
}
|
|
64
84
|
function noop() {
|
|
65
85
|
// Intentionally empty default unsubscribe callback.
|
|
66
86
|
}
|
|
@@ -153,6 +173,8 @@ function parsePromptStreamingBehavior(value) {
|
|
|
153
173
|
throw new Error('Prompt streamingBehavior must be "steer" or "followUp"');
|
|
154
174
|
}
|
|
155
175
|
const GLOBAL_AGENT_CONTEXT_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
|
|
176
|
+
const MANAGED_RELAY_SKILL_DIRECTORY = join(".pi", "skills", "relay");
|
|
177
|
+
const BUNDLED_RELAY_SKILL_PATH = fileURLToPath(new URL("../../../skills/relay/SKILL.md", import.meta.url));
|
|
156
178
|
export function filterManagedGlobalContextFiles(cwd, agentDir, base) {
|
|
157
179
|
return {
|
|
158
180
|
agentsFiles: base.agentsFiles.filter((file) => {
|
|
@@ -168,34 +190,77 @@ export function filterManagedGlobalContextFiles(cwd, agentDir, base) {
|
|
|
168
190
|
}
|
|
169
191
|
export function filterManagedProjectSkills(cwd, base) {
|
|
170
192
|
const projectRoot = realpathSync(cwd);
|
|
193
|
+
const pathAllowed = (path) => pathInsideRealDirectory(projectRoot, path);
|
|
171
194
|
return {
|
|
172
195
|
...base,
|
|
173
|
-
skills: base.skills.filter((skill) =>
|
|
174
|
-
|
|
175
|
-
return pathInsideOrEqual(projectRoot, realpathSync(skill.filePath));
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
}),
|
|
196
|
+
skills: base.skills.filter((skill) => pathAllowed(skill.filePath)),
|
|
197
|
+
diagnostics: filterManagedSkillDiagnostics(base.diagnostics, pathAllowed),
|
|
181
198
|
};
|
|
182
199
|
}
|
|
200
|
+
export async function ensureManagedRelaySkill(cwd, bundledSkillPath = BUNDLED_RELAY_SKILL_PATH) {
|
|
201
|
+
const projectRoot = realpathSync(cwd);
|
|
202
|
+
const piDirectory = resolve(projectRoot, ".pi");
|
|
203
|
+
const skillsDirectory = join(piDirectory, "skills");
|
|
204
|
+
const skillDirectory = resolve(projectRoot, MANAGED_RELAY_SKILL_DIRECTORY);
|
|
205
|
+
const skillPath = join(skillDirectory, "SKILL.md");
|
|
206
|
+
for (const directory of [piDirectory, skillsDirectory, skillDirectory]) {
|
|
207
|
+
await ensureManagedSkillDirectory(projectRoot, directory);
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
await copyFile(bundledSkillPath, skillPath, constants.COPYFILE_EXCL);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (!isNodeError(error, "EEXIST"))
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
assertManagedSkillPath(projectRoot, skillPath, "file");
|
|
217
|
+
return skillPath;
|
|
218
|
+
}
|
|
183
219
|
export function filterManagedWorkbenchSkills(cwd, base, receipt) {
|
|
184
220
|
const projectRoot = realpathSync(cwd);
|
|
185
|
-
const allowedDirectories = new Set(
|
|
221
|
+
const allowedDirectories = new Set([
|
|
222
|
+
resolve(projectRoot, MANAGED_RELAY_SKILL_DIRECTORY),
|
|
223
|
+
...receipt.skills.map((skill) => resolve(projectRoot, ".pi", "skills", skill.directory)),
|
|
224
|
+
]);
|
|
225
|
+
const pathAllowed = (path) => [...allowedDirectories].some((directory) => pathInsideRealDirectory(directory, path));
|
|
186
226
|
return {
|
|
187
227
|
...base,
|
|
188
|
-
skills: base.skills.filter((skill) =>
|
|
189
|
-
|
|
190
|
-
const path = realpathSync(skill.filePath);
|
|
191
|
-
return [...allowedDirectories].some((directory) => pathInsideOrEqual(directory, path));
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
}),
|
|
228
|
+
skills: base.skills.filter((skill) => pathAllowed(skill.filePath)),
|
|
229
|
+
diagnostics: filterManagedSkillDiagnostics(base.diagnostics, pathAllowed),
|
|
197
230
|
};
|
|
198
231
|
}
|
|
232
|
+
function filterManagedSkillDiagnostics(diagnostics, pathAllowed) {
|
|
233
|
+
return diagnostics.filter((diagnostic) => diagnostic.path === undefined || pathAllowed(diagnostic.path));
|
|
234
|
+
}
|
|
235
|
+
function pathInsideRealDirectory(directory, path) {
|
|
236
|
+
try {
|
|
237
|
+
return pathInsideOrEqual(directory, realpathSync(path));
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function ensureManagedSkillDirectory(projectRoot, path) {
|
|
244
|
+
try {
|
|
245
|
+
await mkdir(path);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (!isNodeError(error, "EEXIST"))
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
assertManagedSkillPath(projectRoot, path, "directory");
|
|
252
|
+
}
|
|
253
|
+
function assertManagedSkillPath(projectRoot, path, expectedType) {
|
|
254
|
+
const realPath = realpathSync(path);
|
|
255
|
+
const stats = statSync(realPath);
|
|
256
|
+
const hasExpectedType = expectedType === "directory" ? stats.isDirectory() : stats.isFile();
|
|
257
|
+
if (!pathInsideOrEqual(projectRoot, realPath) || !hasExpectedType) {
|
|
258
|
+
throw new Error(`Managed skill path is invalid: ${path}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function isNodeError(error, code) {
|
|
262
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
263
|
+
}
|
|
199
264
|
function pathInsideOrEqual(parent, child) {
|
|
200
265
|
const normalizedParent = resolve(parent);
|
|
201
266
|
const normalizedChild = resolve(child);
|
|
@@ -371,6 +436,7 @@ function createDefaultRuntimeFactory(normalModelRuntimeForCwd, managementModelRu
|
|
|
371
436
|
}
|
|
372
437
|
function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spawn, subsessions, askUser, managementContext, workbench, logger, managementAudit, managementProjectIdForCwd) {
|
|
373
438
|
return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled = true }) => {
|
|
439
|
+
await ensureManagedRelaySkill(cwd);
|
|
374
440
|
const auditIdentity = await resolveManagementAuditIdentity(cwd, managementContext, managementProjectIdForCwd, logger);
|
|
375
441
|
const accessHandle = workbenchAccessHandle(managementContext);
|
|
376
442
|
const state = workbench === undefined ? undefined : workbench.accessStates.require(accessHandle);
|
|
@@ -402,8 +468,23 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
402
468
|
cwd,
|
|
403
469
|
},
|
|
404
470
|
});
|
|
405
|
-
const
|
|
406
|
-
|
|
471
|
+
const scopedSpawn = spawn === undefined
|
|
472
|
+
? undefined
|
|
473
|
+
: (input) => spawn({ ...input, managementContext });
|
|
474
|
+
const scopedSubsessions = subsessions === undefined
|
|
475
|
+
? undefined
|
|
476
|
+
: {
|
|
477
|
+
...subsessions,
|
|
478
|
+
spawn: (input) => subsessions.spawn({ ...input, managementContext }),
|
|
479
|
+
};
|
|
480
|
+
const controlledManagementTools = [
|
|
481
|
+
...(delegationToolsEnabled && scopedSpawn !== undefined ? [defineTool(createSpawnSessionToolDefinition(cwd, { spawn: scopedSpawn }))] : []),
|
|
482
|
+
...(delegationToolsEnabled && scopedSubsessions !== undefined ? createSubsessionToolDefinitions(cwd, scopedSubsessions).map((tool) => defineTool(tool)) : []),
|
|
483
|
+
...(askUser === undefined ? [] : [createAskUserToolDefinition(askUser)]),
|
|
484
|
+
...workbenchTools,
|
|
485
|
+
];
|
|
486
|
+
const controlledManagementToolNames = controlledManagementTools.map((tool) => tool.name);
|
|
487
|
+
const policyAgentDir = await writeManagementPermissionSystemPolicy(agentDir, cwd, managementContext, controlledManagementToolNames);
|
|
407
488
|
return withRuntimeCreationEnvironment({ [PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR]: policyAgentDir }, async () => {
|
|
408
489
|
const settingsManager = await createScopedSettingsManager({
|
|
409
490
|
cwd,
|
|
@@ -421,29 +502,17 @@ function createManagementRuntimeFactory(modelRuntime, resolveSettingsScope, spaw
|
|
|
421
502
|
},
|
|
422
503
|
});
|
|
423
504
|
const managedToolOptions = createManagedAgentToolOptions(cwd);
|
|
424
|
-
const scopedSpawn = spawn === undefined
|
|
425
|
-
? undefined
|
|
426
|
-
: (input) => spawn({ ...input, managementContext });
|
|
427
|
-
const scopedSubsessions = subsessions === undefined
|
|
428
|
-
? undefined
|
|
429
|
-
: {
|
|
430
|
-
...subsessions,
|
|
431
|
-
spawn: (input) => subsessions.spawn({ ...input, managementContext }),
|
|
432
|
-
};
|
|
433
505
|
const customTools = [
|
|
434
506
|
...managedAgentToolDefinitions(cwd, managedToolOptions),
|
|
435
507
|
createManagedPythonToolDefinition(cwd, managementContext),
|
|
436
|
-
...
|
|
437
|
-
...(delegationToolsEnabled && scopedSubsessions !== undefined ? createSubsessionToolDefinitions(cwd, scopedSubsessions).map((tool) => defineTool(tool)) : []),
|
|
438
|
-
...(askUser === undefined ? [] : [createAskUserToolDefinition(askUser)]),
|
|
439
|
-
...workbenchTools,
|
|
508
|
+
...controlledManagementTools,
|
|
440
509
|
];
|
|
441
510
|
const options = {
|
|
442
511
|
services,
|
|
443
512
|
sessionManager,
|
|
444
513
|
...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
|
|
445
514
|
customTools,
|
|
446
|
-
tools: managementAgentToolNames(managementContext,
|
|
515
|
+
tools: managementAgentToolNames(managementContext, controlledManagementToolNames),
|
|
447
516
|
...(initialModel === undefined ? {} : { model: initialModel }),
|
|
448
517
|
...(initialThinkingLevel === undefined ? {} : { thinkingLevel: initialThinkingLevel }),
|
|
449
518
|
};
|
|
@@ -1654,6 +1723,7 @@ export class PiSessionService {
|
|
|
1654
1723
|
void this.submitPrompt(session, promptText, behavior, images, echoUserMessage);
|
|
1655
1724
|
}
|
|
1656
1725
|
submitPrompt(session, text, behavior, images = [], echoUserMessage = true) {
|
|
1726
|
+
this.auditUserPrompt(session, text);
|
|
1657
1727
|
this.publishActivity(session, behavior === "steer" ? "插队消息已排队" : behavior === "followUp" ? "消息已排队" : "消息已接收", "active");
|
|
1658
1728
|
const eventScope = this.eventScopeForSession(session);
|
|
1659
1729
|
if (behavior === undefined && echoUserMessage)
|
|
@@ -2491,6 +2561,7 @@ export class PiSessionService {
|
|
|
2491
2561
|
active.unsubscribe = session.subscribe((event) => {
|
|
2492
2562
|
this.events.publish(session.sessionId, toClientEvent(event, session.thinkingLevel), active.eventScope);
|
|
2493
2563
|
this.auditToolExecutionEvent(active, event);
|
|
2564
|
+
this.auditAssistantResponseEvent(active, event);
|
|
2494
2565
|
this.publishActivityForEvent(session, event);
|
|
2495
2566
|
const eventType = getString(event, "type");
|
|
2496
2567
|
if (eventType === "agent_end")
|
|
@@ -2839,7 +2910,12 @@ export class PiSessionService {
|
|
|
2839
2910
|
}, "Pi tool execution audit");
|
|
2840
2911
|
if (managementIdentity !== undefined) {
|
|
2841
2912
|
try {
|
|
2842
|
-
this.managementAudit?.record({
|
|
2913
|
+
this.managementAudit?.record({
|
|
2914
|
+
action: "tool_execution",
|
|
2915
|
+
...managementIdentity,
|
|
2916
|
+
...auditEvent,
|
|
2917
|
+
content: isEnd ? getProperty(event, "result") : getProperty(event, "args"),
|
|
2918
|
+
});
|
|
2843
2919
|
}
|
|
2844
2920
|
catch (error) {
|
|
2845
2921
|
this.logger.info({ sessionId: auditEvent.sessionId, toolCallId: auditEvent.toolCallId, error: error instanceof Error ? error.message : String(error) }, "failed to enqueue management-mode tool audit");
|
|
@@ -2859,6 +2935,43 @@ export class PiSessionService {
|
|
|
2859
2935
|
}, "failed to persist ordinary-mode tool audit");
|
|
2860
2936
|
}
|
|
2861
2937
|
}
|
|
2938
|
+
auditAssistantResponseEvent(active, event) {
|
|
2939
|
+
if (getString(event, "type") !== "message_end")
|
|
2940
|
+
return;
|
|
2941
|
+
const message = getProperty(event, "message");
|
|
2942
|
+
if (getString(message, "role") !== "assistant")
|
|
2943
|
+
return;
|
|
2944
|
+
this.recordManagementContentAudit(active, "assistant_response", auditVisibleAssistantMessage(message));
|
|
2945
|
+
}
|
|
2946
|
+
auditUserPrompt(session, text) {
|
|
2947
|
+
const active = [...new Set(this.active.values())]
|
|
2948
|
+
.find(candidate => candidate.runtime.session === session);
|
|
2949
|
+
if (active === undefined)
|
|
2950
|
+
return;
|
|
2951
|
+
this.recordManagementContentAudit(active, "user_prompt", text);
|
|
2952
|
+
}
|
|
2953
|
+
recordManagementContentAudit(active, action, content) {
|
|
2954
|
+
const identity = active.managementAuditIdentity;
|
|
2955
|
+
if (identity === undefined || this.managementAudit === undefined)
|
|
2956
|
+
return;
|
|
2957
|
+
try {
|
|
2958
|
+
this.managementAudit.record({
|
|
2959
|
+
action,
|
|
2960
|
+
status: "completed",
|
|
2961
|
+
...identity,
|
|
2962
|
+
sessionId: active.runtime.session.sessionId,
|
|
2963
|
+
cwd: active.runtime.cwd,
|
|
2964
|
+
content,
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
catch (error) {
|
|
2968
|
+
this.logger.info({
|
|
2969
|
+
sessionId: active.runtime.session.sessionId,
|
|
2970
|
+
action,
|
|
2971
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2972
|
+
}, "failed to enqueue management-mode content audit");
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2862
2975
|
/**
|
|
2863
2976
|
* Build the reporter for one session construction.
|
|
2864
2977
|
*
|
|
@@ -3817,6 +3930,11 @@ async function resolveManagementAuditIdentity(cwd, context, resolveProjectId, lo
|
|
|
3817
3930
|
logger.info({ userId: context.user.id, cwd, error: error instanceof Error ? error.message : String(error) }, "failed to resolve management audit project; using the authorized default");
|
|
3818
3931
|
}
|
|
3819
3932
|
}
|
|
3820
|
-
return {
|
|
3933
|
+
return {
|
|
3934
|
+
userId: context.user.id,
|
|
3935
|
+
rootUserId: context.user.rootUserId,
|
|
3936
|
+
...(context.user.displayName === undefined ? {} : { userDisplayName: context.user.displayName }),
|
|
3937
|
+
projectId,
|
|
3938
|
+
};
|
|
3821
3939
|
}
|
|
3822
3940
|
//# sourceMappingURL=piSessionService.js.map
|