Files
my-pi/pi-permission-system/src/authority/local-user-authorizer.ts
T
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 2aa1be0b54 feat: 组合包配置以包内 config/ 为唯一来源,并新增受保护写入人工审批
配置统一:权限策略、硬防御、Tool Search、提示词封面以及新迁入的
RTK、pi-compaction、pi-worktree、pi-hermes-memory 配置全部由扩展
直接读取已安装组合包内的 config/,pi install / pi update 即完成部署;
删除 rebuild-bundle-config.mjs 与 bundle-manifest.json,my-pi/ 只保留
日志与缓存。update.sh 成功升级后将默认 git 来源的已安装副本
reset 回发布版本。AGENTS.md 新增「组合包配置原则」。

脚本整合:search_config.sh / ssh_config.sh 合并为 configure.sh
search|ssh;install.sh / update.sh 共享函数抽到 scripts/lib.sh,远程
安装器改为先下载到临时文件再执行,下载失败可被检测。

权限:hard-guards 新增 protectedWrites,写入 Pi 凭据/设置、已安装
组合包与扩展目录、pi-ssh 凭据库时一律人工确认(跳过 AutoReview、
yolo 与 Session grant,无 UI 时拒绝);放行 ls、LSP 查询、ctx 只读、
web_search、Chrome 只读与 git 只读命令,skill_manage 改为 ask。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-20 01:05:31 -07:00

99 lines
3.6 KiB
TypeScript

import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
import type {
PermissionPromptDecision,
RequestPermissionOptions,
} from "#src/authority/permission-dialog";
import type {
PermissionPromptUi,
PromptPreferences,
requestPermissionDecision,
} from "#src/authority/permission-prompt-component";
import { buildForwardedScopeLabels } from "#src/pattern-suggest";
import {
emitUiPromptEvent,
type PermissionEventBus,
} from "#src/permission-events";
import { buildUiPrompt } from "#src/permission-ui-prompt";
import type { TerminalAuthorizer } from "./authorizer";
import type { PromptPermissionDetails } from "./permission-prompter";
/** Dependencies required by {@link LocalUserAuthorizer}. */
export interface LocalUserAuthorizerDeps {
/** The active session's UI surface (select/input plus the inline `custom` dialog). */
ui: PermissionPromptUi;
/** The session run mode; the dispatcher renders the inline dialog only in `"tui"`. */
mode: ExtensionContext["mode"];
/** Event bus used for the `permissions:ui_prompt` broadcast. */
events: PermissionEventBus;
/** Read live at prompt time so a settings-modal toggle takes effect on the next prompt. */
getPromptPreferences: () => PromptPreferences;
/** Injected for testability; production callers pass the real function. */
requestPermissionDecision: typeof requestPermissionDecision;
}
/**
* Authorizer for a session with an active UI: prompt the human here.
*
* Emits the `permissions:ui_prompt` broadcast (moved here from
* `PermissionPrompter`'s `ctx.hasUI` arm) before showing the dialog, so
* observers know a decision is imminent. This is the single emit site: a
* forwarded ask carries its provenance on `details.forwarding`, which this
* class renders (populated `forwarding` context + "(Subagent)" title) so the
* broadcast stays non-degraded (#292) without a second emission path.
*/
export class LocalUserAuthorizer implements TerminalAuthorizer {
constructor(private readonly deps: LocalUserAuthorizerDeps) {}
async authorize(
details: PromptPermissionDetails,
): Promise<PermissionPromptDecision> {
const uiPrompt = buildUiPrompt(details);
emitUiPromptEvent(this.deps.events, uiPrompt);
return this.deps.requestPermissionDecision(
{
mode: this.deps.mode,
ui: this.deps.ui,
...this.deps.getPromptPreferences(),
},
details.forwarding
? "Permission Required (Subagent)"
: "Permission Required",
details.payload,
buildRequestOptions(details),
);
}
}
/**
* A forwarded ask carrying a session-approval suggestion offers the scope
* choice (subagent vs whole session); any other ask keeps its single
* "for this session" option (custom label when the gate supplied one).
*/
function buildRequestOptions(
details: PromptPermissionDetails,
): RequestPermissionOptions | undefined {
const pattern = details.sessionApproval?.patterns[0];
const notes = details.deferNotes;
const withNotes =
notes && notes.length > 0 ? { deferNotes: notes } : undefined;
// A protected-write ask offers no session grant at all — a human must
// confirm every write, so the option is hidden rather than unrecorded.
if (details.humanOnly) {
return { hideSessionOption: true, ...withNotes };
}
if (details.forwarding && details.sessionApproval && pattern) {
return {
sessionScope: buildForwardedScopeLabels(
details.forwarding.requesterAgentName,
details.sessionApproval.surface,
pattern,
),
...withNotes,
};
}
if (details.sessionLabel) {
return { sessionLabel: details.sessionLabel, ...withNotes };
}
return withNotes;
}