@gotgenes/pi-permission-system 31.0.2 → 31.1.1
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/CHANGELOG.md +23 -0
- package/dist/public.d.ts +15 -15
- package/docs/configuration.md +6 -2
- package/docs/cross-extension-api.md +1 -1
- package/docs/session-approvals.md +2 -2
- package/package.json +1 -1
- package/src/access-intent/access-intent.ts +1 -1
- package/src/access-intent/bash/bash-path-resolver.ts +11 -14
- package/src/access-intent/bash/command-enumeration.ts +6 -12
- package/src/access-intent/bash/msys-bash-tokens.ts +1 -1
- package/src/access-intent/bash/nested-execution.ts +1 -1
- package/src/access-intent/bash/node-text.ts +2 -2
- package/src/access-intent/bash/parser.ts +1 -1
- package/src/access-intent/bash/program.ts +4 -7
- package/src/access-intent/bash/redirect-analysis.ts +2 -2
- package/src/access-intent/bash/shell-variable-expansion.ts +1 -1
- package/src/access-intent/bash/sync-commands.ts +2 -5
- package/src/access-intent/bash/token-collection.ts +6 -9
- package/src/access-intent/bash/wrapper-analysis.ts +1 -1
- package/src/access-intent/input-normalizer.ts +2 -2
- package/src/access-intent/path-normalization.ts +1 -1
- package/src/access-intent/path-surfaces.ts +1 -1
- package/src/access-intent/tool-input-path.ts +1 -1
- package/src/access-intent/tool-kind.ts +1 -1
- package/src/authority/approval-escalator.ts +13 -13
- package/src/authority/authorizer-chain.ts +1 -1
- package/src/authority/authorizer-registry.ts +1 -1
- package/src/authority/authorizer-selection.ts +1 -1
- package/src/authority/authorizer.ts +9 -9
- package/src/authority/decision-resolution.ts +2 -5
- package/src/authority/denying-authorizer.ts +1 -1
- package/src/authority/forwarded-request-server.ts +21 -24
- package/src/authority/forwarder-context.ts +2 -2
- package/src/authority/forwarding-io.ts +14 -12
- package/src/authority/forwarding-liveness.ts +5 -8
- package/src/authority/forwarding-manager.ts +1 -1
- package/src/authority/inherited-registrations.ts +2 -2
- package/src/authority/local-user-authorizer.ts +13 -13
- package/src/authority/permission-dialog.ts +2 -2
- package/src/authority/permission-forwarding.ts +6 -3
- package/src/authority/permission-prompt-component.ts +12 -15
- package/src/authority/permission-prompt-decision.ts +2 -2
- package/src/authority/permission-prompter.ts +8 -8
- package/src/authority/subagent-context.ts +3 -3
- package/src/authority/subagent-detection.ts +3 -3
- package/src/{config-loader.ts → config/config-loader.ts} +3 -3
- package/src/{config-modal.ts → config/config-modal.ts} +1 -2
- package/src/{config-reporter.ts → config/config-reporter.ts} +1 -1
- package/src/{config-store.ts → config/config-store.ts} +1 -2
- package/src/{extension-config.ts → config/extension-config.ts} +4 -4
- package/src/{extension-paths.ts → config/extension-paths.ts} +1 -1
- package/src/{policy-loader.ts → config/policy-loader.ts} +2 -2
- package/src/{skill-prompt-sanitizer.ts → exposure/skill-prompt-sanitizer.ts} +2 -2
- package/src/{tool-registry.ts → exposure/tool-registry.ts} +1 -1
- package/src/exposure/tool-surface-baseline.ts +101 -0
- package/src/handlers/before-agent-start.ts +48 -21
- package/src/handlers/gates/bash-command.ts +2 -2
- package/src/handlers/gates/bash-external-directory.ts +3 -3
- package/src/handlers/gates/bash-path-extractor.ts +1 -1
- package/src/handlers/gates/bash-path.ts +4 -4
- package/src/handlers/gates/descriptor.ts +2 -2
- package/src/handlers/gates/external-directory-policy.ts +2 -2
- package/src/handlers/gates/external-directory.ts +4 -4
- package/src/handlers/gates/helpers.ts +1 -1
- package/src/handlers/gates/path.ts +4 -4
- package/src/handlers/gates/runner.ts +4 -4
- package/src/handlers/gates/skill-read.ts +3 -3
- package/src/handlers/gates/tool-call-gate-pipeline.ts +7 -7
- package/src/handlers/gates/tool.ts +3 -3
- package/src/handlers/lifecycle.ts +6 -7
- package/src/handlers/permission-gate-handler.ts +11 -11
- package/src/handlers/session-turn-prep.ts +1 -1
- package/src/handlers/tool-call-boundary.ts +3 -3
- package/src/index.ts +40 -39
- package/src/{decision-reporter.ts → logging/decision-reporter.ts} +1 -1
- package/src/{logging.ts → logging/logging.ts} +1 -1
- package/src/{session-logger.ts → logging/session-logger.ts} +14 -5
- package/src/path/approval-pattern.ts +1 -1
- package/src/path/canonicalize-path.ts +1 -1
- package/src/path/path-containment.ts +2 -2
- package/src/path/path-flavor.ts +1 -1
- package/src/{path-normalizer.ts → path/path-normalizer.ts} +6 -8
- package/src/path/pi-infrastructure-read.ts +3 -3
- package/src/{normalize.ts → policy/normalize.ts} +2 -2
- package/src/{permission-gate.ts → policy/permission-gate.ts} +1 -1
- package/src/{permission-manager.ts → policy/permission-manager.ts} +21 -15
- package/src/{permission-merge.ts → policy/permission-merge.ts} +1 -1
- package/src/{permission-resolver.ts → policy/permission-resolver.ts} +5 -5
- package/src/{rule.ts → policy/rule.ts} +3 -4
- package/src/{scope-merge.ts → policy/scope-merge.ts} +3 -3
- package/src/{synthesize.ts → policy/synthesize.ts} +1 -1
- package/src/{wildcard-matcher.ts → policy/wildcard-matcher.ts} +1 -1
- package/src/presentation/agent-renderer.ts +5 -5
- package/src/presentation/dialog-renderer.ts +3 -3
- package/src/presentation/fact-vocabulary.ts +1 -4
- package/src/presentation/forwarded-ask-payload.ts +1 -4
- package/src/presentation/path-ask-payload.ts +2 -5
- package/src/{pattern-suggest.ts → presentation/pattern-suggest.ts} +6 -3
- package/src/{permission-prompts.ts → presentation/permission-prompts.ts} +1 -1
- package/src/presentation/review-log-renderer.ts +1 -1
- package/src/presentation/skill-ask-payload.ts +3 -3
- package/src/presentation/tool-ask-payload.ts +3 -6
- package/src/{bash-advisory-check.ts → service/bash-advisory-check.ts} +1 -1
- package/src/{permissions-service.ts → service/permissions-service.ts} +10 -10
- package/src/{service-lifecycle.ts → service/service-lifecycle.ts} +5 -5
- package/src/service.ts +18 -18
- package/src/{permission-session.ts → session/permission-session.ts} +39 -15
- package/src/{session-approval.ts → session/session-approval.ts} +2 -2
- package/src/{session-rules.ts → session/session-rules.ts} +1 -1
- package/src/{builtin-tool-input-formatters.ts → tool-input/builtin-tool-input-formatters.ts} +1 -1
- package/src/{tool-input-preview.ts → tool-input/tool-input-preview.ts} +2 -2
- package/src/{tool-input-prompt-formatters.ts → tool-input/tool-input-prompt-formatters.ts} +1 -1
- package/src/{tool-preview-formatter.ts → tool-input/tool-preview-formatter.ts} +3 -3
- package/src/types.ts +2 -2
- /package/src/{async-cache.ts → access-intent/bash/async-cache.ts} +0 -0
- /package/src/{bash-arity.ts → access-intent/bash/bash-arity.ts} +0 -0
- /package/src/{config-paths.ts → config/config-paths.ts} +0 -0
- /package/src/{config-schema.ts → config/config-schema.ts} +0 -0
- /package/src/{status.ts → config/status.ts} +0 -0
- /package/src/{yaml-frontmatter.ts → config/yaml-frontmatter.ts} +0 -0
- /package/src/{system-prompt-sanitizer.ts → exposure/system-prompt-sanitizer.ts} +0 -0
- /package/src/{decision-audit.ts → logging/decision-audit.ts} +0 -0
- /package/src/{json-safe-stringify.ts → logging/json-safe-stringify.ts} +0 -0
- /package/src/{log-field-cap.ts → logging/log-field-cap.ts} +0 -0
- /package/src/{log-file-permissions.ts → logging/log-file-permissions.ts} +0 -0
- /package/src/{log-redaction.ts → logging/log-redaction.ts} +0 -0
- /package/src/{expand-home.ts → path/expand-home.ts} +0 -0
- /package/src/{node-modules-discovery.ts → path/node-modules-discovery.ts} +0 -0
- /package/src/{safe-system-paths.ts → path/safe-system-paths.ts} +0 -0
- /package/src/{restrictiveness.ts → policy/restrictiveness.ts} +0 -0
- /package/src/{permission-events.ts → service/permission-events.ts} +0 -0
- /package/src/{permission-ui-prompt.ts → service/permission-ui-prompt.ts} +0 -0
- /package/src/{active-agent.ts → session/active-agent.ts} +0 -0
- /package/src/{approval-grant.ts → session/approval-grant.ts} +0 -0
- /package/src/{session-approval-recorder.ts → session/session-approval-recorder.ts} +0 -0
- /package/src/{session-identity.ts → session/session-identity.ts} +0 -0
- /package/src/{tool-access-extractor-registry.ts → tool-input/tool-access-extractor-registry.ts} +0 -0
- /package/src/{tool-input-formatter-registry.ts → tool-input/tool-input-formatter-registry.ts} +0 -0
|
@@ -18,23 +18,20 @@
|
|
|
18
18
|
|
|
19
19
|
import { readdirSync, readFileSync } from "node:fs";
|
|
20
20
|
import { join } from "node:path";
|
|
21
|
+
import type { DebugReviewLogger } from "#src/logging/session-logger";
|
|
21
22
|
import {
|
|
22
23
|
ensureDirectoryExists,
|
|
23
24
|
isErrnoCode,
|
|
24
25
|
logPermissionForwardingError,
|
|
25
26
|
safeDeleteFile,
|
|
26
27
|
writeJsonFileAtomic,
|
|
27
|
-
} from "
|
|
28
|
-
import type { PermissionForwardingTarget } from "
|
|
28
|
+
} from "./forwarding-io";
|
|
29
|
+
import type { PermissionForwardingTarget } from "./permission-forwarding";
|
|
29
30
|
import {
|
|
30
31
|
encodeSessionIdForPath,
|
|
31
32
|
PERMISSION_FORWARDING_POLL_INTERVAL_MS,
|
|
32
|
-
} from "
|
|
33
|
-
import type {
|
|
34
|
-
ServingAnnouncer,
|
|
35
|
-
ServingLookup,
|
|
36
|
-
} from "#src/authority/serving-registry";
|
|
37
|
-
import type { DebugReviewLogger } from "#src/session-logger";
|
|
33
|
+
} from "./permission-forwarding";
|
|
34
|
+
import type { ServingAnnouncer, ServingLookup } from "./serving-registry";
|
|
38
35
|
|
|
39
36
|
/**
|
|
40
37
|
* How often a serving session rewrites its heartbeat — four poll ticks.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { DebugReviewLogger } from "#src/session-logger";
|
|
2
|
+
import type { DebugReviewLogger } from "#src/logging/session-logger";
|
|
3
3
|
import type { InboxProcessor } from "./forwarded-request-server";
|
|
4
4
|
import { getSessionId } from "./forwarder-context";
|
|
5
5
|
import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding";
|
|
@@ -36,11 +36,11 @@ import type { PermissionsService } from "#src/service";
|
|
|
36
36
|
import type {
|
|
37
37
|
ResolvedToolAccessExtractor,
|
|
38
38
|
ToolAccessExtractorLookup,
|
|
39
|
-
} from "#src/tool-access-extractor-registry";
|
|
39
|
+
} from "#src/tool-input/tool-access-extractor-registry";
|
|
40
40
|
import type {
|
|
41
41
|
ToolInputFormatter,
|
|
42
42
|
ToolInputFormatterLookup,
|
|
43
|
-
} from "#src/tool-input-formatter-registry";
|
|
43
|
+
} from "#src/tool-input/tool-input-formatter-registry";
|
|
44
44
|
|
|
45
45
|
/** This node's own session id, or `null` when the host exposes none. */
|
|
46
46
|
export interface NodeIdentity {
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { provenDirectionOf } from "#src/approval-grant";
|
|
3
|
-
import type {
|
|
4
|
-
PermissionPromptDecision,
|
|
5
|
-
RequestPermissionOptions,
|
|
6
|
-
} from "#src/authority/permission-dialog";
|
|
7
|
-
import type {
|
|
8
|
-
PermissionPromptUi,
|
|
9
|
-
PromptPreferences,
|
|
10
|
-
requestPermissionDecision,
|
|
11
|
-
} from "#src/authority/permission-prompt-component";
|
|
12
2
|
import {
|
|
13
3
|
buildDirectionalSessionLabels,
|
|
14
4
|
buildForwardedScopeLabels,
|
|
15
5
|
describeGrantTarget,
|
|
16
|
-
} from "#src/pattern-suggest";
|
|
6
|
+
} from "#src/presentation/pattern-suggest";
|
|
17
7
|
import {
|
|
18
8
|
emitUiPromptEvent,
|
|
19
9
|
type PermissionEventBus,
|
|
20
|
-
} from "#src/permission-events";
|
|
21
|
-
import { buildUiPrompt } from "#src/permission-ui-prompt";
|
|
10
|
+
} from "#src/service/permission-events";
|
|
11
|
+
import { buildUiPrompt } from "#src/service/permission-ui-prompt";
|
|
12
|
+
import { provenDirectionOf } from "#src/session/approval-grant";
|
|
22
13
|
import type { TerminalAuthorizer } from "./authorizer";
|
|
14
|
+
import type {
|
|
15
|
+
PermissionPromptDecision,
|
|
16
|
+
RequestPermissionOptions,
|
|
17
|
+
} from "./permission-dialog";
|
|
18
|
+
import type {
|
|
19
|
+
PermissionPromptUi,
|
|
20
|
+
PromptPreferences,
|
|
21
|
+
requestPermissionDecision,
|
|
22
|
+
} from "./permission-prompt-component";
|
|
23
23
|
import type { PromptPermissionDetails } from "./permission-prompter";
|
|
24
24
|
|
|
25
25
|
/** Dependencies required by {@link LocalUserAuthorizer}. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SessionGrantWidth } from "#src/approval-grant";
|
|
2
|
-
import type { DecisionSource } from "
|
|
1
|
+
import type { SessionGrantWidth } from "#src/session/approval-grant";
|
|
2
|
+
import type { DecisionSource } from "./decision-source";
|
|
3
3
|
|
|
4
4
|
export type PermissionDecisionState =
|
|
5
5
|
| "approved"
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import type { ApprovalGrant, SessionGrantWidth } from "#src/approval-grant";
|
|
3
|
-
import type { DecisionSource } from "#src/authority/decision-source";
|
|
4
|
-
import type { PermissionUiPromptSource } from "#src/permission-events";
|
|
5
2
|
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
3
|
+
import type { PermissionUiPromptSource } from "#src/service/permission-events";
|
|
4
|
+
import type {
|
|
5
|
+
ApprovalGrant,
|
|
6
|
+
SessionGrantWidth,
|
|
7
|
+
} from "#src/session/approval-grant";
|
|
8
|
+
import type { DecisionSource } from "./decision-source";
|
|
6
9
|
import type { PermissionDecisionState } from "./permission-dialog";
|
|
7
10
|
import type { SubagentSessionRegistry } from "./subagent-registry";
|
|
8
11
|
|
|
@@ -4,17 +4,22 @@ import type {
|
|
|
4
4
|
KeybindingsManager,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { type Component, Input, matchesKey } from "@earendil-works/pi-tui";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import {
|
|
8
|
+
completeViewBudget,
|
|
9
|
+
type DialogView,
|
|
10
|
+
type RenderBudget,
|
|
11
|
+
renderPromptDialog,
|
|
12
|
+
} from "#src/presentation/dialog-renderer";
|
|
13
|
+
import { fitLinesToWidth } from "#src/presentation/line-fitting";
|
|
14
|
+
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
15
|
+
import { collapsePastedNewlines } from "./bracketed-paste";
|
|
16
|
+
import type { DecisionSource, UserDecisionSurface } from "./decision-source";
|
|
12
17
|
import {
|
|
13
18
|
type PermissionPromptDecision,
|
|
14
19
|
type RequestPermissionOptions,
|
|
15
20
|
requestPermissionDecisionFromUi,
|
|
16
21
|
type UnattributedDecision,
|
|
17
|
-
} from "
|
|
22
|
+
} from "./permission-dialog";
|
|
18
23
|
import {
|
|
19
24
|
initialPromptState,
|
|
20
25
|
type PromptEvent,
|
|
@@ -23,15 +28,7 @@ import {
|
|
|
23
28
|
type PromptViewState,
|
|
24
29
|
reducePrompt,
|
|
25
30
|
visibleOptionKeys,
|
|
26
|
-
} from "
|
|
27
|
-
import {
|
|
28
|
-
completeViewBudget,
|
|
29
|
-
type DialogView,
|
|
30
|
-
type RenderBudget,
|
|
31
|
-
renderPromptDialog,
|
|
32
|
-
} from "#src/presentation/dialog-renderer";
|
|
33
|
-
import { fitLinesToWidth } from "#src/presentation/line-fitting";
|
|
34
|
-
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
31
|
+
} from "./permission-prompt-decision";
|
|
35
32
|
|
|
36
33
|
/**
|
|
37
34
|
* Inline `ctx.ui.custom` permission dialog for TUI sessions.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { SessionGrantWidth } from "#src/approval-grant";
|
|
1
|
+
import type { SessionGrantWidth } from "#src/session/approval-grant";
|
|
2
2
|
import {
|
|
3
3
|
createDeniedPermissionDecision,
|
|
4
4
|
normalizePermissionDenialReason,
|
|
5
5
|
type RequestPermissionOptions,
|
|
6
6
|
type UnattributedDecision,
|
|
7
|
-
} from "
|
|
7
|
+
} from "./permission-dialog";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Pure decision model for the inline keybind permission dialog.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { DecisionSource } from "#src/authority/decision-source";
|
|
3
|
-
import type { PermissionPromptDecision } from "#src/authority/permission-dialog";
|
|
4
|
-
import type {
|
|
5
|
-
ForwardedAccessFacts,
|
|
6
|
-
ForwardedSessionApproval,
|
|
7
|
-
} from "#src/authority/permission-forwarding";
|
|
1
|
+
import type { ReviewLogger } from "#src/logging/session-logger";
|
|
8
2
|
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
9
3
|
import { renderReviewLogFacts } from "#src/presentation/review-log-renderer";
|
|
10
|
-
import type {
|
|
4
|
+
import type { SessionGrantWidth } from "#src/session/approval-grant";
|
|
11
5
|
import type { TerminalAuthorizer } from "./authorizer";
|
|
6
|
+
import type { DecisionSource } from "./decision-source";
|
|
7
|
+
import type { PermissionPromptDecision } from "./permission-dialog";
|
|
8
|
+
import type {
|
|
9
|
+
ForwardedAccessFacts,
|
|
10
|
+
ForwardedSessionApproval,
|
|
11
|
+
} from "./permission-forwarding";
|
|
12
12
|
|
|
13
13
|
export type PermissionReviewSource = "tool_call" | "skill_input" | "skill_read";
|
|
14
14
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { SUBAGENT_ENV_HINT_KEYS } from "#src/authority/permission-forwarding";
|
|
2
|
-
import type { SubagentSessionRegistry } from "#src/authority/subagent-registry";
|
|
3
1
|
import type { PathFlavor } from "#src/path/path-flavor";
|
|
4
|
-
import { readSessionId } from "#src/session-identity";
|
|
2
|
+
import { readSessionId } from "#src/session/session-identity";
|
|
3
|
+
import { SUBAGENT_ENV_HINT_KEYS } from "./permission-forwarding";
|
|
4
|
+
import type { SubagentSessionRegistry } from "./subagent-registry";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Narrow context for subagent detection — the only session-manager readers
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { PathFlavor } from "#src/path/path-flavor";
|
|
1
2
|
import {
|
|
2
3
|
isSubagentExecutionContext,
|
|
3
4
|
type SubagentDetectionContext,
|
|
4
|
-
} from "
|
|
5
|
-
import type { SubagentSessionRegistry } from "
|
|
6
|
-
import type { PathFlavor } from "#src/path/path-flavor";
|
|
5
|
+
} from "./subagent-context";
|
|
6
|
+
import type { SubagentSessionRegistry } from "./subagent-registry";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Narrow seam for the ask-path consumers: "is the current session a subagent?"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { normalize } from "node:path";
|
|
3
3
|
import type { ZodError } from "zod";
|
|
4
|
+
import { mergeFlatPermissions } from "#src/policy/permission-merge";
|
|
5
|
+
import type { FlatPermissionConfig, PatternValue } from "#src/types";
|
|
6
|
+
import { isDenyWithReason, isPermissionState } from "#src/types";
|
|
4
7
|
import {
|
|
5
8
|
getGlobalConfigPath,
|
|
6
9
|
getLegacyExtensionConfigPath,
|
|
@@ -13,9 +16,6 @@ import {
|
|
|
13
16
|
type UnifiedPermissionConfig,
|
|
14
17
|
unifiedConfigSchema,
|
|
15
18
|
} from "./config-schema";
|
|
16
|
-
import { mergeFlatPermissions } from "./permission-merge";
|
|
17
|
-
import type { FlatPermissionConfig, PatternValue } from "./types";
|
|
18
|
-
import { isDenyWithReason, isPermissionState } from "./types";
|
|
19
19
|
|
|
20
20
|
// The unified config shape is derived from the zod schema (config-schema.ts,
|
|
21
21
|
// the single source of truth) and re-exported so existing importers keep their
|
|
@@ -4,13 +4,12 @@ import {
|
|
|
4
4
|
getSettingsListTheme,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { type SettingItem, SettingsList } from "@earendil-works/pi-tui";
|
|
7
|
-
|
|
7
|
+
import type { Ruleset } from "#src/policy/rule";
|
|
8
8
|
import type { CommandConfigStore } from "./config-store";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_EXTENSION_CONFIG,
|
|
11
11
|
type PermissionSystemExtensionConfig,
|
|
12
12
|
} from "./extension-config";
|
|
13
|
-
import type { Ruleset } from "./rule";
|
|
14
13
|
|
|
15
14
|
interface PermissionSystemConfigController {
|
|
16
15
|
config: CommandConfigStore;
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
ExtensionCommandContext,
|
|
11
11
|
ExtensionContext,
|
|
12
12
|
} from "@earendil-works/pi-coding-agent";
|
|
13
|
-
|
|
13
|
+
import type { DebugReviewLogger } from "#src/logging/session-logger";
|
|
14
14
|
import { loadAndMergeConfigs, loadUnifiedConfig } from "./config-loader";
|
|
15
15
|
import {
|
|
16
16
|
getGlobalConfigPath,
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
type PermissionSystemExtensionConfig,
|
|
27
27
|
} from "./extension-config";
|
|
28
28
|
import type { ResolvedPolicyPaths } from "./policy-loader";
|
|
29
|
-
import type { DebugReviewLogger } from "./session-logger";
|
|
30
29
|
import { syncPermissionSystemStatus } from "./status";
|
|
31
30
|
|
|
32
31
|
/** Read-only view of the current config — for consumers that only read. */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import {
|
|
5
|
+
OWNER_ONLY_DIRECTORY_MODE,
|
|
6
|
+
restrictExistingPathToOwner,
|
|
7
|
+
} from "#src/logging/log-file-permissions";
|
|
4
8
|
import type {
|
|
5
9
|
ShellToolsConfig,
|
|
6
10
|
UnifiedPermissionConfig,
|
|
7
11
|
} from "./config-loader";
|
|
8
|
-
import {
|
|
9
|
-
OWNER_ONLY_DIRECTORY_MODE,
|
|
10
|
-
restrictExistingPathToOwner,
|
|
11
|
-
} from "./log-file-permissions";
|
|
12
12
|
|
|
13
13
|
export const EXTENSION_ID = "pi-permission-system";
|
|
14
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { discoverGlobalNodeModulesRoot } from "#src/path/node-modules-discovery";
|
|
2
3
|
import { getGlobalLogsDir } from "./config-paths";
|
|
3
|
-
import { discoverGlobalNodeModulesRoot } from "./node-modules-discovery";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Immutable path constants derived from `agentDir` at construction time.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { ScopeConfig } from "#src/types";
|
|
5
|
+
import { toRecord } from "#src/value-guards";
|
|
4
6
|
import {
|
|
5
7
|
loadUnifiedConfig,
|
|
6
8
|
normalizeFlatPermissionValue,
|
|
7
9
|
stripJsonComments,
|
|
8
10
|
} from "./config-loader";
|
|
9
11
|
import { getGlobalConfigPath } from "./config-paths";
|
|
10
|
-
import type { ScopeConfig } from "./types";
|
|
11
|
-
import { toRecord } from "./value-guards";
|
|
12
12
|
import { extractFrontmatter, parseSimpleYamlMap } from "./yaml-frontmatter";
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
2
|
|
|
3
|
-
import type { PathNormalizer } from "
|
|
4
|
-
import type { PermissionCheckResult, PermissionState } from "
|
|
3
|
+
import type { PathNormalizer } from "#src/path/path-normalizer";
|
|
4
|
+
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Narrow interface for the raw (no-session-rules) permission checker used by
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** What a turn observed about pi's tool surface. */
|
|
2
|
+
export interface ToolSurfaceObservation {
|
|
3
|
+
/** Names pi reports active right now (`pi.getActiveTools()`). */
|
|
4
|
+
readonly active: readonly string[];
|
|
5
|
+
/** Names pi currently has registered (`pi.getAllTools()`). */
|
|
6
|
+
readonly registered: ReadonlySet<string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** The effective tool surface for one turn, and what changed to produce it. */
|
|
10
|
+
export interface ToolSurfaceResolution {
|
|
11
|
+
/** Names to hand to `setActive`, in baseline order. */
|
|
12
|
+
readonly exposed: readonly string[];
|
|
13
|
+
/** Baseline members the current policy withholds. */
|
|
14
|
+
readonly withheld: readonly string[];
|
|
15
|
+
/** Names withheld on an earlier turn that the current policy exposes again. */
|
|
16
|
+
readonly restored: readonly string[];
|
|
17
|
+
/** Whether the withheld set differs from the previous turn's. */
|
|
18
|
+
readonly changed: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Decides whether the current policy lets the agent see a tool. */
|
|
22
|
+
type ToolExposurePolicy = (toolName: string) => boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The runtime tool surface a session has, independent of what policy withholds.
|
|
26
|
+
*
|
|
27
|
+
* Filtering writes its result back through `setActive`, so reading the active
|
|
28
|
+
* set again next turn returns the *filtered* set. Applying policy to that makes
|
|
29
|
+
* the surface monotonically shrink and leaves a tool stranded once its rule is
|
|
30
|
+
* relaxed (#873). This baseline is the stable input policy is applied to
|
|
31
|
+
* instead: `exposed = baseline ∩ policy`, recomputed every turn.
|
|
32
|
+
*
|
|
33
|
+
* The baseline only ever grows from tools observed **active**, never from the
|
|
34
|
+
* whole registry, so a tool pi deliberately left inactive is never activated
|
|
35
|
+
* (#385). A tool that stops being active without this extension withholding it
|
|
36
|
+
* — another extension deactivating it — leaves the baseline with it.
|
|
37
|
+
*/
|
|
38
|
+
export class ToolSurfaceBaseline {
|
|
39
|
+
private baseline: readonly string[] = [];
|
|
40
|
+
private withheld: ReadonlySet<string> = new Set();
|
|
41
|
+
|
|
42
|
+
resolveExposed(
|
|
43
|
+
observation: ToolSurfaceObservation,
|
|
44
|
+
isExposed: ToolExposurePolicy,
|
|
45
|
+
): ToolSurfaceResolution {
|
|
46
|
+
const baseline = this.rebuild(observation);
|
|
47
|
+
const exposed: string[] = [];
|
|
48
|
+
const withheld: string[] = [];
|
|
49
|
+
for (const toolName of baseline) {
|
|
50
|
+
(isExposed(toolName) ? exposed : withheld).push(toolName);
|
|
51
|
+
}
|
|
52
|
+
const restored = exposed.filter((toolName) => this.withheld.has(toolName));
|
|
53
|
+
const changed = !holdsExactly(this.withheld, withheld);
|
|
54
|
+
|
|
55
|
+
this.baseline = baseline;
|
|
56
|
+
this.withheld = new Set(withheld);
|
|
57
|
+
return { exposed, withheld, restored, changed };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Forget the surface, so the next turn reseeds from what pi reports. */
|
|
61
|
+
reset(): void {
|
|
62
|
+
this.baseline = [];
|
|
63
|
+
this.withheld = new Set();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reconstruct the pre-filter surface: the tools still active, plus the ones
|
|
68
|
+
* only this extension's own filtering removed, plus anything newly active.
|
|
69
|
+
*
|
|
70
|
+
* A withheld tool that has left the registry is forgotten rather than kept as
|
|
71
|
+
* a restoration candidate, so re-registering it inactive cannot activate it.
|
|
72
|
+
* The registry is consulted for withheld tools only — an active tool is real
|
|
73
|
+
* by definition — and an active tool is adopted whatever the registry says,
|
|
74
|
+
* so a registry that reports nothing can cost restoration candidates but
|
|
75
|
+
* never removes a tool pi has active.
|
|
76
|
+
*/
|
|
77
|
+
private rebuild(observation: ToolSurfaceObservation): readonly string[] {
|
|
78
|
+
const active = new Set(observation.active);
|
|
79
|
+
const baseline = this.baseline.filter(
|
|
80
|
+
(toolName) =>
|
|
81
|
+
active.has(toolName) ||
|
|
82
|
+
(this.withheld.has(toolName) && observation.registered.has(toolName)),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const known = new Set(baseline);
|
|
86
|
+
for (const toolName of observation.active) {
|
|
87
|
+
if (!known.has(toolName)) {
|
|
88
|
+
known.add(toolName);
|
|
89
|
+
baseline.push(toolName);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return baseline;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function holdsExactly(
|
|
97
|
+
set: ReadonlySet<string>,
|
|
98
|
+
members: readonly string[],
|
|
99
|
+
): boolean {
|
|
100
|
+
return set.size === members.length && members.every((m) => set.has(m));
|
|
101
|
+
}
|
|
@@ -2,12 +2,17 @@ import type {
|
|
|
2
2
|
BeforeAgentStartEventResult,
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import { resolveSkillPromptEntries } from "#src/exposure/skill-prompt-sanitizer";
|
|
6
|
+
import { sanitizeAvailableToolsSection } from "#src/exposure/system-prompt-sanitizer";
|
|
7
|
+
import {
|
|
8
|
+
getToolNameFromValue,
|
|
9
|
+
type ToolRegistry,
|
|
10
|
+
} from "#src/exposure/tool-registry";
|
|
11
|
+
import type { ToolSurfaceObservation } from "#src/exposure/tool-surface-baseline";
|
|
12
|
+
import type { DebugLogger } from "#src/logging/session-logger";
|
|
13
|
+
import type { PermissionResolver } from "#src/policy/permission-resolver";
|
|
14
|
+
import type { PermissionSession } from "#src/session/permission-session";
|
|
15
|
+
import type { TurnPreparation } from "./session-turn-prep";
|
|
11
16
|
|
|
12
17
|
/** Minimal subset of BeforeAgentStartEvent used by this handler. */
|
|
13
18
|
interface BeforeAgentStartPayload {
|
|
@@ -42,7 +47,11 @@ export function shouldExposeTool(
|
|
|
42
47
|
* session state
|
|
43
48
|
* - `session` — encapsulates all mutable session state and lifecycle operations
|
|
44
49
|
* - `resolver` — owns permission-query surface: `isToolFullyDenied`, skill check
|
|
45
|
-
* - `toolRegistry` — Pi tool API subset (getActive + setActive)
|
|
50
|
+
* - `toolRegistry` — Pi tool API subset (getAll + getActive + setActive)
|
|
51
|
+
* - `logger` — records each change to the effective tool surface
|
|
52
|
+
*
|
|
53
|
+
* The active set is recomputed from the session's pre-filter tool surface
|
|
54
|
+
* every turn, so relaxing a rule restores the tool it had withheld (#873).
|
|
46
55
|
*/
|
|
47
56
|
export class AgentPrepHandler {
|
|
48
57
|
constructor(
|
|
@@ -50,6 +59,7 @@ export class AgentPrepHandler {
|
|
|
50
59
|
private readonly session: PermissionSession,
|
|
51
60
|
private readonly resolver: PermissionResolver,
|
|
52
61
|
private readonly toolRegistry: ToolRegistry,
|
|
62
|
+
private readonly logger: DebugLogger,
|
|
53
63
|
) {}
|
|
54
64
|
|
|
55
65
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -60,24 +70,23 @@ export class AgentPrepHandler {
|
|
|
60
70
|
this.turnPrep.prepare(ctx);
|
|
61
71
|
|
|
62
72
|
const agentName = this.session.resolveAgentName(ctx, event.systemPrompt);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for (const tool of activeTools) {
|
|
67
|
-
const toolName = getToolNameFromValue(tool);
|
|
68
|
-
if (!toolName) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (
|
|
73
|
+
const surface = this.session.resolveExposedTools(
|
|
74
|
+
this.observeToolSurface(),
|
|
75
|
+
(toolName) =>
|
|
72
76
|
shouldExposeTool(toolName, agentName, (t, a) =>
|
|
73
77
|
this.resolver.isToolFullyDenied(t, a),
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
}
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
const allowedTools = [...surface.exposed];
|
|
79
81
|
|
|
80
82
|
this.toolRegistry.setActive(allowedTools);
|
|
83
|
+
if (surface.changed) {
|
|
84
|
+
this.logger.debug("tool_surface.changed", {
|
|
85
|
+
exposed: surface.exposed,
|
|
86
|
+
withheld: surface.withheld,
|
|
87
|
+
restored: surface.restored,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
81
90
|
|
|
82
91
|
const toolPromptResult = sanitizeAvailableToolsSection(
|
|
83
92
|
event.systemPrompt,
|
|
@@ -94,4 +103,22 @@ export class AgentPrepHandler {
|
|
|
94
103
|
? { systemPrompt: skillPromptResult.prompt }
|
|
95
104
|
: {};
|
|
96
105
|
}
|
|
106
|
+
|
|
107
|
+
private observeToolSurface(): ToolSurfaceObservation {
|
|
108
|
+
return {
|
|
109
|
+
active: toolNamesOf(this.toolRegistry.getActive()),
|
|
110
|
+
registered: new Set(toolNamesOf(this.toolRegistry.getAll())),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function toolNamesOf(tools: readonly unknown[]): string[] {
|
|
116
|
+
const names: string[] = [];
|
|
117
|
+
for (const tool of tools) {
|
|
118
|
+
const toolName = getToolNameFromValue(tool);
|
|
119
|
+
if (toolName) {
|
|
120
|
+
names.push(toolName);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return names;
|
|
97
124
|
}
|
|
@@ -2,8 +2,8 @@ import type {
|
|
|
2
2
|
BashCommand,
|
|
3
3
|
WrapperKind,
|
|
4
4
|
} from "#src/access-intent/bash/command-enumeration";
|
|
5
|
-
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
6
|
-
import { pickMostRestrictive } from "#src/restrictiveness";
|
|
5
|
+
import type { ScopedPermissionResolver } from "#src/policy/permission-resolver";
|
|
6
|
+
import { pickMostRestrictive } from "#src/policy/restrictiveness";
|
|
7
7
|
import type { PermissionCheckResult } from "#src/types";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { BashProgram } from "#src/access-intent/bash/program";
|
|
2
|
-
import type { PathNormalizer } from "#src/path-normalizer";
|
|
3
|
-
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
2
|
+
import type { PathNormalizer } from "#src/path/path-normalizer";
|
|
3
|
+
import type { ScopedPermissionResolver } from "#src/policy/permission-resolver";
|
|
4
4
|
import { buildBashExternalDirectoryAskPayload } from "#src/presentation/path-ask-payload";
|
|
5
|
-
import { SessionApproval } from "#src/session-approval";
|
|
5
|
+
import { SessionApproval } from "#src/session/session-approval";
|
|
6
6
|
import type { GateResult } from "./descriptor";
|
|
7
7
|
import { selectUncoveredExternalPaths } from "./external-directory-policy";
|
|
8
8
|
import { accessFactsFromPath } from "./helpers";
|
|
@@ -2,11 +2,11 @@ import type { AccessPath } from "#src/access-intent/access-path";
|
|
|
2
2
|
import type { BashProgram } from "#src/access-intent/bash/program";
|
|
3
3
|
import type { TokenEffect } from "#src/access-intent/effect";
|
|
4
4
|
import { capabilitySurfaceForEffect } from "#src/access-intent/path-surfaces";
|
|
5
|
-
import type { PathNormalizer } from "#src/path-normalizer";
|
|
6
|
-
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
5
|
+
import type { PathNormalizer } from "#src/path/path-normalizer";
|
|
6
|
+
import type { ScopedPermissionResolver } from "#src/policy/permission-resolver";
|
|
7
|
+
import { pickMostRestrictive } from "#src/policy/restrictiveness";
|
|
7
8
|
import { buildPathAskPayload } from "#src/presentation/path-ask-payload";
|
|
8
|
-
import {
|
|
9
|
-
import { SessionApproval } from "#src/session-approval";
|
|
9
|
+
import { SessionApproval } from "#src/session/session-approval";
|
|
10
10
|
import type { PermissionCheckResult } from "#src/types";
|
|
11
11
|
import type { GateResult } from "./descriptor";
|
|
12
12
|
import { accessFactsFromPath } from "./helpers";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { DecisionSource } from "#src/authority/decision-source";
|
|
2
2
|
import type { PromptPermissionDetails } from "#src/authority/permission-prompter";
|
|
3
|
-
import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
4
3
|
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
5
|
-
import type {
|
|
4
|
+
import type { PermissionDecisionEvent } from "#src/service/permission-events";
|
|
5
|
+
import type { SessionApproval } from "#src/session/session-approval";
|
|
6
6
|
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
7
7
|
|
|
8
8
|
// ── Descriptor types ───────────────────────────────────────────────────────
|
|
@@ -2,8 +2,8 @@ import type { AccessPath } from "#src/access-intent/access-path";
|
|
|
2
2
|
import type { BashExternalPath } from "#src/access-intent/bash/bash-path-resolver";
|
|
3
3
|
import type { TokenEffect } from "#src/access-intent/effect";
|
|
4
4
|
import { capabilitySurfaceForEffect } from "#src/access-intent/path-surfaces";
|
|
5
|
-
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
6
|
-
import { pickMostRestrictive } from "#src/restrictiveness";
|
|
5
|
+
import type { ScopedPermissionResolver } from "#src/policy/permission-resolver";
|
|
6
|
+
import { pickMostRestrictive } from "#src/policy/restrictiveness";
|
|
7
7
|
import type { PermissionCheckResult } from "#src/types";
|
|
8
8
|
|
|
9
9
|
/** An external path whose resolved `external_directory` state is not "allow". */
|