@exagone313/dsh-podman 0.2.0-rc.3 → 0.2.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/README.md +14 -2
- package/README.zh.md +14 -2
- package/cordis.patch.yml +58 -0
- package/dist/approval-reasons.d.ts +3 -4
- package/dist/approval-reasons.js +40 -49
- package/dist/approval-reasons.test.js +43 -15
- package/dist/approval.js +27 -10
- package/dist/approval.test.js +165 -34
- package/dist/card-route.d.ts +3 -2
- package/dist/card-route.js +154 -42
- package/dist/card-route.test.js +85 -43
- package/dist/card-test-support.js +39 -66
- package/dist/client/ContainerCard.d.ts +1 -1
- package/dist/client/ContainerCard.js +92 -50
- package/dist/client/card-protocol.d.ts +6 -3
- package/dist/client/container-card-caches.js +11 -6
- package/dist/client/container-card-controller.d.ts +16 -18
- package/dist/client/container-card-controller.js +92 -40
- package/dist/client/container-card-create-modal.d.ts +2 -2
- package/dist/client/container-card-create-modal.js +13 -5
- package/dist/client/container-card-default-env.d.ts +10 -0
- package/dist/client/container-card-default-env.js +69 -0
- package/dist/client/container-card-directory.d.ts +2 -2
- package/dist/client/container-card-directory.js +12 -4
- package/dist/client/container-card-editors.d.ts +3 -3
- package/dist/client/container-card-editors.js +104 -32
- package/dist/client/container-card-images.d.ts +4 -4
- package/dist/client/container-card-images.js +19 -6
- package/dist/client/container-card-paths.d.ts +3 -3
- package/dist/client/container-card-paths.js +18 -11
- package/dist/client/container-card-row.d.ts +10 -6
- package/dist/client/container-card-row.js +82 -13
- package/dist/client/container-card-secrets.d.ts +3 -3
- package/dist/client/container-card-secrets.js +8 -6
- package/dist/client/container-card-shared.d.ts +4 -17
- package/dist/client/container-card-shared.js +9 -25
- package/dist/client/container-card-styles.d.ts +2 -8
- package/dist/client/container-card-styles.js +14 -52
- package/dist/client/container-card-volumes.d.ts +2 -2
- package/dist/client/container-card-volumes.js +9 -5
- package/dist/client/container-card-workspace.d.ts +8 -6
- package/dist/client/container-card-workspace.js +9 -3
- package/dist/client/directory-picker.js +2 -1
- package/dist/client/index.js +13141 -2036
- package/dist/client/locales.d.ts +6 -1
- package/dist/client/locales.js +106 -38
- package/dist/client/podman-terminal-guide.d.ts +11 -0
- package/dist/client/podman-terminal-guide.js +208 -0
- package/dist/client/podman-terminal-title.d.ts +7 -0
- package/dist/client/podman-terminal-title.js +30 -0
- package/dist/client/podman-terminal.d.ts +26 -0
- package/dist/client/podman-terminal.js +412 -0
- package/dist/client/read-only-approval.d.ts +2 -2
- package/dist/client/read-only-approval.js +13 -3
- package/dist/client/slot-contract.d.ts +0 -11
- package/dist/client/terminal-preference.d.ts +26 -0
- package/dist/client/terminal-preference.js +67 -0
- package/dist/client/terminal-protocol.d.ts +85 -0
- package/dist/client/terminal-protocol.js +14 -0
- package/dist/client/terminal-tab.d.ts +60 -0
- package/dist/client/terminal-tab.js +35 -0
- package/dist/client/terminal-targets.d.ts +33 -0
- package/dist/client/terminal-targets.js +65 -0
- package/dist/client/terminal-titles.d.ts +23 -0
- package/dist/client/terminal-titles.js +52 -0
- package/dist/client/terminal-transport.d.ts +63 -0
- package/dist/client/terminal-transport.js +207 -0
- package/dist/client/tool-views.js +215 -49
- package/dist/container-env.d.ts +11 -0
- package/dist/container-env.js +96 -0
- package/dist/container-env.test.d.ts +1 -0
- package/dist/container-env.test.js +93 -0
- package/dist/containers.test.js +196 -18
- package/dist/daemons.test.js +18 -4
- package/dist/dsh-version.d.ts +1 -0
- package/dist/dsh-version.js +46 -0
- package/dist/env-rows.d.ts +14 -0
- package/dist/env-rows.js +52 -0
- package/dist/env-rows.test.d.ts +1 -0
- package/dist/env-rows.test.js +43 -0
- package/dist/fs-provider.d.ts +1 -0
- package/dist/fs-provider.js +26 -12
- package/dist/fs-provider.test.js +51 -10
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +2 -2
- package/dist/grpc/proto/dshctl/v1/control.proto +11 -1
- package/dist/grpc/proto/dshguest/v1/guest.proto +5 -0
- package/dist/grpc/runtime-client.d.ts +3 -1
- package/dist/grpc/runtime-client.js +90 -17
- package/dist/guest-rpc.d.ts +15 -19
- package/dist/guest-rpc.js +289 -70
- package/dist/guest-rpc.test.d.ts +1 -0
- package/dist/guest-rpc.test.js +241 -0
- package/dist/guest-terminal.d.ts +30 -0
- package/dist/guest-terminal.js +196 -0
- package/dist/images.test.js +10 -3
- package/dist/index.d.ts +11 -16
- package/dist/index.js +42 -36
- package/dist/locales.test.d.ts +1 -0
- package/dist/locales.test.js +34 -0
- package/dist/misc.test.js +38 -10
- package/dist/mount-enums.js +2 -1
- package/dist/mount-input.d.ts +2 -0
- package/dist/mount-input.js +38 -22
- package/dist/mounts.test.js +81 -11
- package/dist/naming.test.d.ts +1 -0
- package/dist/naming.test.js +28 -0
- package/dist/output-reader.js +36 -4
- package/dist/package-deps.test.d.ts +1 -0
- package/dist/package-deps.test.js +49 -0
- package/dist/paths.test.js +4 -2
- package/dist/plugin-meta.test.d.ts +1 -0
- package/dist/plugin-meta.test.js +29 -0
- package/dist/project-path.js +33 -7
- package/dist/project-path.test.js +14 -0
- package/dist/prompts.d.ts +0 -4
- package/dist/prompts.js +15 -84
- package/dist/read-only-shell.js +3 -1
- package/dist/read-only-shell.test.js +80 -25
- package/dist/runtime-client.test.d.ts +1 -0
- package/dist/runtime-client.test.js +53 -0
- package/dist/secrets.test.js +21 -6
- package/dist/settings-commands.test.js +61 -14
- package/dist/settings-create.test.js +32 -10
- package/dist/settings-default-env.test.d.ts +1 -0
- package/dist/settings-default-env.test.js +175 -0
- package/dist/settings-mounts.test.js +46 -7
- package/dist/settings-schema.d.ts +6 -11
- package/dist/settings-schema.js +4 -8
- package/dist/settings-schema.test.d.ts +1 -0
- package/dist/settings-schema.test.js +35 -0
- package/dist/settings-workspaces.test.js +38 -63
- package/dist/spill-store.d.ts +1 -0
- package/dist/spill-store.js +17 -4
- package/dist/spill-store.test.js +31 -8
- package/dist/subprocess.d.ts +4 -0
- package/dist/subprocess.js +148 -162
- package/dist/subprocess.test.js +118 -8
- package/dist/terminal-preference.test.d.ts +1 -0
- package/dist/terminal-preference.test.js +62 -0
- package/dist/terminal-route.d.ts +10 -0
- package/dist/terminal-route.js +270 -0
- package/dist/terminal-route.test.d.ts +1 -0
- package/dist/terminal-route.test.js +265 -0
- package/dist/terminal-sessions.d.ts +69 -0
- package/dist/terminal-sessions.js +252 -0
- package/dist/terminal-shells.d.ts +22 -0
- package/dist/terminal-shells.js +99 -0
- package/dist/terminal-tab.test.d.ts +1 -0
- package/dist/terminal-tab.test.js +58 -0
- package/dist/terminal-targets.test.d.ts +1 -0
- package/dist/terminal-targets.test.js +43 -0
- package/dist/terminal-titles.test.d.ts +1 -0
- package/dist/terminal-titles.test.js +36 -0
- package/dist/test-support.d.ts +35 -5
- package/dist/test-support.js +91 -29
- package/dist/tool-defs.js +19 -4
- package/dist/tool-handlers.js +80 -28
- package/dist/tool-params.js +19 -5
- package/dist/version-compat.d.ts +4 -0
- package/dist/version-compat.js +29 -0
- package/dist/version-compat.test.d.ts +1 -0
- package/dist/version-compat.test.js +27 -0
- package/dist/volumes.test.js +1 -1
- package/dist/workspace-binding.d.ts +19 -10
- package/dist/workspace-binding.js +252 -78
- package/dist/workspace-binding.test.js +202 -27
- package/docs/architecture.md +71 -20
- package/docs/architecture.zh.md +62 -12
- package/docs/configuration.md +61 -20
- package/docs/configuration.zh.md +53 -19
- package/docs/development-prompt.md +72 -0
- package/docs/development-prompt.zh.md +69 -0
- package/docs/development.md +184 -29
- package/docs/development.zh.md +165 -26
- package/docs/install-dsh-and-dsh-podman.md +24 -21
- package/docs/install-dsh-and-dsh-podman.zh.md +20 -21
- package/docs/uninstall.md +83 -0
- package/docs/uninstall.zh.md +79 -0
- package/docs/update.md +120 -0
- package/docs/update.zh.md +114 -0
- package/docs/usage.md +108 -61
- package/docs/usage.zh.md +76 -38
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +58 -25
- package/quadlet/dsh-podman-orchestrator.container +46 -0
- package/quadlet/dsh.container +35 -0
- package/LICENSE.pkg +0 -17103
- package/dist/preferences.d.ts +0 -6
- package/dist/preferences.js +0 -27
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ SPDX-License-Identifier: MIT
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://www.npmjs.com/package/@exagone313/dsh-podman)
|
|
11
11
|
[](https://github.com/Exagone313/dsh-podman/releases)
|
|
12
|
+
[](https://github.com/Exagone313/dsh-podman/pkgs/container/dsh-podman%2Fdsh)
|
|
12
13
|
[](https://github.com/Exagone313/dsh-podman/pkgs/container/dsh-podman%2Forchestrator)
|
|
13
14
|
[](https://github.com/Exagone313/dsh-podman/pkgs/container/dsh-podman%2Fguest-agent)
|
|
14
15
|
|
|
@@ -20,21 +21,32 @@ English | [中文](README.zh.md)
|
|
|
20
21
|
dsh-podman routes all shell execution and file access through disposable and
|
|
21
22
|
per-project Podman containers.
|
|
22
23
|
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- Disposable, per-project containers
|
|
27
|
+
- Additional containers per workspace
|
|
28
|
+
- Create container images based on Arch Linux, Ubuntu and Alpine Linux
|
|
29
|
+
- Approval-aware escalation
|
|
30
|
+
- Run processes in containers (daemons)
|
|
31
|
+
- Run a terminal in containers
|
|
32
|
+
|
|
23
33
|
## Documentation
|
|
24
34
|
|
|
25
35
|
- **[Install DeepSeek Harness & dsh-podman with rootless Podman](docs/install-dsh-and-dsh-podman.md)**
|
|
36
|
+
- [Update](docs/update.md)
|
|
26
37
|
- [Architecture](docs/architecture.md)
|
|
27
38
|
- [Configuration](docs/configuration.md)
|
|
28
39
|
- [Usage](docs/usage.md)
|
|
29
40
|
- [Development](docs/development.md)
|
|
41
|
+
- [Uninstall](docs/uninstall.md)
|
|
30
42
|
|
|
31
43
|
## AI-usage disclosure
|
|
32
44
|
|
|
33
45
|
This repository was written in major parts using AI models:
|
|
34
46
|
|
|
47
|
+
- DeepSeek Flash
|
|
35
48
|
- early commits using OpenAI GPT-5.6 Luna
|
|
36
|
-
-
|
|
37
|
-
- source code weakness fixes by Anthropic Claude Opus 5
|
|
49
|
+
- a few fixes using Anthropic Claude Opus 5
|
|
38
50
|
|
|
39
51
|
## License
|
|
40
52
|
|
package/README.zh.md
CHANGED
|
@@ -9,6 +9,7 @@ SPDX-License-Identifier: MIT
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://www.npmjs.com/package/@exagone313/dsh-podman)
|
|
11
11
|
[](https://github.com/Exagone313/dsh-podman/releases)
|
|
12
|
+
[](https://github.com/Exagone313/dsh-podman/pkgs/container/dsh-podman%2Fdsh)
|
|
12
13
|
[](https://github.com/Exagone313/dsh-podman/pkgs/container/dsh-podman%2Forchestrator)
|
|
13
14
|
[](https://github.com/Exagone313/dsh-podman/pkgs/container/dsh-podman%2Fguest-agent)
|
|
14
15
|
|
|
@@ -20,21 +21,32 @@ SPDX-License-Identifier: MIT
|
|
|
20
21
|
dsh-podman 将所有的 shell 执行与文件访问都路由到一次性、按项目隔离的 Podman
|
|
21
22
|
容器中。
|
|
22
23
|
|
|
24
|
+
## 特性
|
|
25
|
+
|
|
26
|
+
- 一次性、按项目隔离的容器
|
|
27
|
+
- 每个工作区可添加额外的容器
|
|
28
|
+
- 创建基于 Arch Linux、Ubuntu 和 Alpine Linux 的容器镜像
|
|
29
|
+
- 感知审批的权限提升
|
|
30
|
+
- 在容器中运行进程(守护进程)
|
|
31
|
+
- 在容器中运行终端
|
|
32
|
+
|
|
23
33
|
## 文档
|
|
24
34
|
|
|
25
35
|
- **[使用 rootless Podman 安装 DeepSeek Harness 与 dsh-podman](docs/install-dsh-and-dsh-podman.zh.md)**
|
|
36
|
+
- [更新](docs/update.zh.md)
|
|
26
37
|
- [架构](docs/architecture.zh.md)
|
|
27
38
|
- [配置](docs/configuration.zh.md)
|
|
28
39
|
- [使用](docs/usage.zh.md)
|
|
29
40
|
- [开发](docs/development.zh.md)
|
|
41
|
+
- [卸载](docs/uninstall.zh.md)
|
|
30
42
|
|
|
31
43
|
## AI 使用声明
|
|
32
44
|
|
|
33
45
|
本仓库的大部分代码由 AI 模型编写:
|
|
34
46
|
|
|
47
|
+
- DeepSeek Flash
|
|
35
48
|
- 早期提交使用 OpenAI GPT-5.6 Luna
|
|
36
|
-
-
|
|
37
|
-
- 源代码弱点修复由 Anthropic Claude Opus 5 完成
|
|
49
|
+
- 少量修复由 Anthropic Claude Opus 5 完成
|
|
38
50
|
|
|
39
51
|
## 许可证
|
|
40
52
|
|
package/cordis.patch.yml
CHANGED
|
@@ -12,6 +12,64 @@
|
|
|
12
12
|
disabled: true
|
|
13
13
|
- id: spill-local
|
|
14
14
|
disabled: true
|
|
15
|
+
# disable built-in terminal.
|
|
16
|
+
- id: ui-sidebar-terminal
|
|
17
|
+
disabled: true
|
|
15
18
|
- insert:
|
|
16
19
|
- id: podman
|
|
17
20
|
name: '@exagone313/dsh-podman'
|
|
21
|
+
# The "Podman operator mode" agent preset, declared as a row so the
|
|
22
|
+
# preset registry serves it. Its id is the approval policy's preset key
|
|
23
|
+
# (PODMAN_OPS_PRESET in src/approval.ts), and the composition below is the
|
|
24
|
+
# agent's plugin set.
|
|
25
|
+
- id: preset-podman-ops
|
|
26
|
+
name: '@deepseek-ai/dsh-agent-preset'
|
|
27
|
+
config:
|
|
28
|
+
id: podman-ops
|
|
29
|
+
name: Podman operator mode
|
|
30
|
+
description: >-
|
|
31
|
+
Podman-focused container operations — manage images, containers,
|
|
32
|
+
volumes, mounts, secrets, and daemons. Inspections and lifecycle run
|
|
33
|
+
directly; commands, container file edits, daemon starts, and secret
|
|
34
|
+
changes ask for approval; supports web research.
|
|
35
|
+
order: 2
|
|
36
|
+
plugins:
|
|
37
|
+
- id: persona
|
|
38
|
+
name: '@deepseek-ai/dsh-persona'
|
|
39
|
+
config:
|
|
40
|
+
suffix: Your working directory is {{cwd}}.
|
|
41
|
+
prefix: >-
|
|
42
|
+
You are a Podman container-operations agent powered by the {{model}} model.
|
|
43
|
+
|
|
44
|
+
You manage container infrastructure through the dsh-podman tools: images
|
|
45
|
+
(image_list, image_get, image_build, image_rebuild, image_rebuild_all,
|
|
46
|
+
image_remove), containers (container_list, container_start,
|
|
47
|
+
container_recreate, container_remove), mounts (container_mount_list,
|
|
48
|
+
container_mount_add, container_mount_remove, container_mount_update),
|
|
49
|
+
PATH additions (container_path_set, container_path_add,
|
|
50
|
+
container_path_remove), volumes (volume_list,
|
|
51
|
+
volume_create, volume_remove), secrets (secret_list, secret_create,
|
|
52
|
+
secret_remove, container_secret_add, container_secret_remove), daemons
|
|
53
|
+
(daemon_list, daemon_start, daemon_stop, daemon_restart, daemon_logs),
|
|
54
|
+
and container inspection (container_read, container_glob,
|
|
55
|
+
container_grep).
|
|
56
|
+
|
|
57
|
+
Inspection and lifecycle operations run directly. Running commands inside
|
|
58
|
+
a container (container_bash, container_exec), editing container files
|
|
59
|
+
(container_write, container_edit), starting daemons (daemon_start),
|
|
60
|
+
exposing or removing secret environment variables (container_secret_add,
|
|
61
|
+
container_secret_remove), removing secrets (secret_remove), and
|
|
62
|
+
rebuilding all images (image_rebuild_all) require the user's approval.
|
|
63
|
+
Use web_search to research images and documentation. Use
|
|
64
|
+
ask_user_question for user decisions and todo_write to track work.
|
|
65
|
+
- id: tool-ask-user
|
|
66
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
67
|
+
- id: tool-todo
|
|
68
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
69
|
+
config:
|
|
70
|
+
allowParallelInProgress: true
|
|
71
|
+
- id: tool-web
|
|
72
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
73
|
+
config:
|
|
74
|
+
fetch: false
|
|
75
|
+
searchTimeoutMs: 60000
|
|
@@ -24,6 +24,7 @@ export type ReasonFact = {
|
|
|
24
24
|
image?: string;
|
|
25
25
|
mounts?: readonly MountFact[];
|
|
26
26
|
env?: readonly string[];
|
|
27
|
+
secretEnv?: readonly string[];
|
|
27
28
|
paths?: readonly string[];
|
|
28
29
|
} | {
|
|
29
30
|
kind: "container_remove";
|
|
@@ -119,11 +120,9 @@ export type DenialFact = {
|
|
|
119
120
|
source?: string;
|
|
120
121
|
mirror?: string;
|
|
121
122
|
};
|
|
122
|
-
export
|
|
123
|
-
get(ns: string): unknown;
|
|
124
|
-
}
|
|
125
|
-
export declare function resolveReasonLocale(settings: SettingsReader | undefined): ReasonLocale;
|
|
123
|
+
export declare function resolveReasonLocale(value: unknown): ReasonLocale;
|
|
126
124
|
export declare function reasonLocale(value: unknown): ReasonLocale;
|
|
127
125
|
export declare function renderReason(locale: ReasonLocale, fact: ReasonFact): string;
|
|
128
126
|
export declare function renderCacheCleanNotice(locale: ReasonLocale, files: number): string;
|
|
127
|
+
export declare function renderDefaultEnvSyncNotice(locale: ReasonLocale, applied: number, skipped: number): string;
|
|
129
128
|
export declare function renderDenial(locale: ReasonLocale, fact: DenialFact): string;
|
package/dist/approval-reasons.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// first (it also captures the browser default), then the durable user
|
|
10
|
-
// preference, then English.
|
|
11
|
-
export function resolveReasonLocale(settings) {
|
|
12
|
-
if (settings === undefined)
|
|
13
|
-
return "en";
|
|
14
|
-
const observed = field(settings.get(CONTAINER_NS), "uiLocale");
|
|
15
|
-
if (typeof observed === "string" && observed !== "")
|
|
16
|
-
return reasonLocale(observed);
|
|
17
|
-
return reasonLocale(field(settings.get(LOCALE_NS), LOCALE_PREFERENCE_FIELD));
|
|
4
|
+
// The locale to render approval text in, from the plugin's own volatile
|
|
5
|
+
// `uiLocale` preference (the browser client writes it); an unset value renders
|
|
6
|
+
// in English.
|
|
7
|
+
export function resolveReasonLocale(value) {
|
|
8
|
+
return reasonLocale(value);
|
|
18
9
|
}
|
|
19
10
|
// Normalize a BCP 47-style locale id to a shipped language; `zh-Hans`, `zh_CN`
|
|
20
11
|
// and friends all map to `zh`, everything else to English.
|
|
@@ -24,11 +15,6 @@ export function reasonLocale(value) {
|
|
|
24
15
|
const primary = value.toLowerCase().split(/[-_]/, 1)[0];
|
|
25
16
|
return primary === "zh" ? "zh" : "en";
|
|
26
17
|
}
|
|
27
|
-
function field(value, key) {
|
|
28
|
-
return typeof value === "object" && value !== null
|
|
29
|
-
? value[key]
|
|
30
|
-
: undefined;
|
|
31
|
-
}
|
|
32
18
|
// Pick the locale's variant of one string.
|
|
33
19
|
function pick(locale, en, zh) {
|
|
34
20
|
return locale === "zh" ? zh : en;
|
|
@@ -68,9 +54,7 @@ function mountLabel(locale, mount) {
|
|
|
68
54
|
// undefined when it has none. The caller supplies the surrounding punctuation,
|
|
69
55
|
// since the locales wrap it differently.
|
|
70
56
|
function mountDestinationText(locale, mount) {
|
|
71
|
-
return mount.destination === undefined
|
|
72
|
-
? undefined
|
|
73
|
-
: pick(locale, `at ${quoted(locale, mount.destination)}`, `挂载到 ${quoted(locale, mount.destination)}`);
|
|
57
|
+
return mount.destination === undefined ? undefined : pick(locale, `at ${quoted(locale, mount.destination)}`, `挂载到 ${quoted(locale, mount.destination)}`);
|
|
74
58
|
}
|
|
75
59
|
// Render a mount's source and destination: `volume "data" at "/data"`.
|
|
76
60
|
function mountSourceText(locale, mount) {
|
|
@@ -94,7 +78,9 @@ function mountModeName(locale, mount) {
|
|
|
94
78
|
// Render one mount as `volume "data" at "/data" (read-only)`.
|
|
95
79
|
function mountText(locale, mount) {
|
|
96
80
|
const mode = mountModeName(locale, mount);
|
|
97
|
-
const suffix = mode === undefined
|
|
81
|
+
const suffix = mode === undefined
|
|
82
|
+
? ""
|
|
83
|
+
: pick(locale, ` (${mode})`, `(${mode})`);
|
|
98
84
|
return `${mountSourceText(locale, mount)}${suffix}`;
|
|
99
85
|
}
|
|
100
86
|
// Render a command word list, capped at 8 words with an ellipsis tail.
|
|
@@ -114,26 +100,25 @@ function identityText(locale, fact) {
|
|
|
114
100
|
if (fact.groups !== undefined && fact.groups.length > 0) {
|
|
115
101
|
ids.push(`groups ${joinList(locale, fact.groups.map((group) => String(group)))}`);
|
|
116
102
|
}
|
|
117
|
-
return ids.length === 0
|
|
103
|
+
return ids.length === 0
|
|
104
|
+
? ""
|
|
105
|
+
: pick(locale, ` (${ids.join(", ")})`, `(${ids.join("、")})`);
|
|
118
106
|
}
|
|
119
107
|
// Start/recreate share one shape; only the verb differs.
|
|
120
108
|
function containerRun(locale, fact) {
|
|
121
|
-
const image = fact.image === undefined
|
|
122
|
-
? ""
|
|
123
|
-
: pick(locale, ` from image ${quoted(locale, fact.image)}`, `(镜像 ${quoted(locale, fact.image)})`);
|
|
109
|
+
const image = fact.image === undefined ? "" : pick(locale, ` from image ${quoted(locale, fact.image)}`, `(镜像 ${quoted(locale, fact.image)})`);
|
|
124
110
|
const mounts = fact.mounts === undefined || fact.mounts.length === 0
|
|
125
111
|
? ""
|
|
126
112
|
: pick(locale, ` with mounts: ${joinList(locale, fact.mounts.map((mount) => mountText(locale, mount)))}`, `,挂载:${joinList(locale, fact.mounts.map((mount) => mountText(locale, mount)))}`);
|
|
127
|
-
const env = fact.env === undefined || fact.env.length === 0
|
|
128
|
-
|
|
129
|
-
: pick(locale, ` with env: ${joinList(locale, fact.env)}`, `,环境变量:${joinList(locale, fact.env)}`);
|
|
130
|
-
const paths = fact.paths === undefined || fact.paths.length === 0
|
|
113
|
+
const env = fact.env === undefined || fact.env.length === 0 ? "" : pick(locale, ` with env: ${joinList(locale, fact.env)}`, `,环境变量:${joinList(locale, fact.env)}`);
|
|
114
|
+
const secretEnv = fact.secretEnv === undefined || fact.secretEnv.length === 0
|
|
131
115
|
? ""
|
|
132
|
-
: pick(locale, ` with
|
|
116
|
+
: pick(locale, ` with secret env: ${joinList(locale, fact.secretEnv)}`, `,机密环境变量:${joinList(locale, fact.secretEnv)}`);
|
|
117
|
+
const paths = fact.paths === undefined || fact.paths.length === 0 ? "" : pick(locale, ` with PATH additions: ${joinList(locale, fact.paths.map((path) => quoted(locale, path)))}`, `,PATH 附加项:${joinList(locale, fact.paths.map((path) => quoted(locale, path)))}`);
|
|
133
118
|
const verb = fact.kind === "container_start"
|
|
134
119
|
? pick(locale, "Start", "启动")
|
|
135
120
|
: pick(locale, "Recreate", "重建");
|
|
136
|
-
return pick(locale, `${verb} container ${quoted(locale, fact.container)}${image}${mounts}${env}${paths}.`, `${verb}容器 ${quoted(locale, fact.container)}${image}${mounts}${env}${paths}。`);
|
|
121
|
+
return pick(locale, `${verb} container ${quoted(locale, fact.container)}${image}${mounts}${env}${secretEnv}${paths}.`, `${verb}容器 ${quoted(locale, fact.container)}${image}${mounts}${env}${secretEnv}${paths}。`);
|
|
137
122
|
}
|
|
138
123
|
// Render one reason as the single-line headline shown in the approval prompt.
|
|
139
124
|
// Reasons that end in a raw command keep no trailing period, so the command
|
|
@@ -141,12 +126,8 @@ function containerRun(locale, fact) {
|
|
|
141
126
|
export function renderReason(locale, fact) {
|
|
142
127
|
switch (fact.kind) {
|
|
143
128
|
case "image_build": {
|
|
144
|
-
const parent = fact.parent === undefined
|
|
145
|
-
|
|
146
|
-
: pick(locale, ` from ${quoted(locale, fact.parent)}`, `(基于 ${quoted(locale, fact.parent)})`);
|
|
147
|
-
const packages = fact.packages === undefined
|
|
148
|
-
? ""
|
|
149
|
-
: pick(locale, ` with packages: ${joinList(locale, fact.packages)}`, `,软件包:${joinList(locale, fact.packages)}`);
|
|
129
|
+
const parent = fact.parent === undefined ? "" : pick(locale, ` from ${quoted(locale, fact.parent)}`, `(基于 ${quoted(locale, fact.parent)})`);
|
|
130
|
+
const packages = fact.packages === undefined ? "" : pick(locale, ` with packages: ${joinList(locale, fact.packages)}`, `,软件包:${joinList(locale, fact.packages)}`);
|
|
150
131
|
return pick(locale, `Build image ${quoted(locale, fact.image)}${parent}${packages}.`, `构建镜像 ${quoted(locale, fact.image)}${parent}${packages}。`);
|
|
151
132
|
}
|
|
152
133
|
case "image_rebuild":
|
|
@@ -188,9 +169,7 @@ export function renderReason(locale, fact) {
|
|
|
188
169
|
const remount = fact.remount
|
|
189
170
|
.map((mount) => `- ${mountText(locale, mount)}`)
|
|
190
171
|
.join("\n");
|
|
191
|
-
const keep = fact.keep.length === 0
|
|
192
|
-
? ""
|
|
193
|
-
: pick(locale, `\n\nKept as-is: ${joinList(locale, fact.keep.map((mount) => mountText(locale, mount)))}.`, `\n\n保持不变:${joinList(locale, fact.keep.map((mount) => mountText(locale, mount)))}。`);
|
|
172
|
+
const keep = fact.keep.length === 0 ? "" : pick(locale, `\n\nKept as-is: ${joinList(locale, fact.keep.map((mount) => mountText(locale, mount)))}.`, `\n\n保持不变:${joinList(locale, fact.keep.map((mount) => mountText(locale, mount)))}。`);
|
|
194
173
|
return pick(locale, `Read-only mode blocks ${quoted(locale, fact.tool)} while a mount is read-write. Remount these mounts read-only to run it:\n\n${remount}${keep}`, `只读模式在存在读写挂载时会阻止 ${quoted(locale, fact.tool)}。将这些挂载重新挂载为只读即可运行:\n\n${remount}${keep}`);
|
|
195
174
|
}
|
|
196
175
|
case "volume_remove":
|
|
@@ -202,16 +181,12 @@ export function renderReason(locale, fact) {
|
|
|
202
181
|
case "container_secret_remove":
|
|
203
182
|
return pick(locale, `Detach secret env var ${quoted(locale, fact.env)} from container ${quoted(locale, fact.container)}.`, `从容器 ${quoted(locale, fact.container)} 中移除机密环境变量 ${quoted(locale, fact.env)}。`);
|
|
204
183
|
case "container_bash": {
|
|
205
|
-
const cwd = fact.cwd === undefined
|
|
206
|
-
? ""
|
|
207
|
-
: pick(locale, ` (cwd ${quoted(locale, fact.cwd)})`, `(工作目录 ${quoted(locale, fact.cwd)})`);
|
|
184
|
+
const cwd = fact.cwd === undefined ? "" : pick(locale, ` (cwd ${quoted(locale, fact.cwd)})`, `(工作目录 ${quoted(locale, fact.cwd)})`);
|
|
208
185
|
const identity = identityText(locale, fact);
|
|
209
186
|
return pick(locale, `Run a shell command in container ${quoted(locale, fact.container)}${cwd}: ${fact.command}${identity}`, `在容器 ${quoted(locale, fact.container)}${cwd} 中运行 shell 命令:${fact.command}${identity}`);
|
|
210
187
|
}
|
|
211
188
|
case "container_exec": {
|
|
212
|
-
const cwd = fact.cwd === undefined
|
|
213
|
-
? ""
|
|
214
|
-
: pick(locale, ` (cwd ${quoted(locale, fact.cwd)})`, `(工作目录 ${quoted(locale, fact.cwd)})`);
|
|
189
|
+
const cwd = fact.cwd === undefined ? "" : pick(locale, ` (cwd ${quoted(locale, fact.cwd)})`, `(工作目录 ${quoted(locale, fact.cwd)})`);
|
|
215
190
|
const identity = identityText(locale, fact);
|
|
216
191
|
return pick(locale, `Run a command in container ${quoted(locale, fact.container)}${cwd}: ${argvText(fact.argv)}${identity}`, `在容器 ${quoted(locale, fact.container)}${cwd} 中运行命令:${argvText(fact.argv)}${identity}`);
|
|
217
192
|
}
|
|
@@ -220,7 +195,9 @@ export function renderReason(locale, fact) {
|
|
|
220
195
|
case "container_edit":
|
|
221
196
|
return pick(locale, `Edit ${quoted(locale, fact.path)} in container ${quoted(locale, fact.container)}.`, `在容器 ${quoted(locale, fact.container)} 中编辑 ${quoted(locale, fact.path)}。`);
|
|
222
197
|
case "daemon_start": {
|
|
223
|
-
const named = fact.name === undefined
|
|
198
|
+
const named = fact.name === undefined
|
|
199
|
+
? ""
|
|
200
|
+
: ` ${quoted(locale, fact.name)}`;
|
|
224
201
|
const suffix = identityText(locale, fact);
|
|
225
202
|
return pick(locale, `Start daemon${named} in container ${quoted(locale, fact.container)}: ${argvText(fact.argv)}${suffix}`, `在容器 ${quoted(locale, fact.container)} 中启动守护进程${named}:${argvText(fact.argv)}${suffix}`);
|
|
226
203
|
}
|
|
@@ -231,6 +208,20 @@ export function renderReason(locale, fact) {
|
|
|
231
208
|
export function renderCacheCleanNotice(locale, files) {
|
|
232
209
|
return pick(locale, `removed ${files} cached ${files === 1 ? "file" : "files"}`, `已移除 ${files} 个缓存文件`);
|
|
233
210
|
}
|
|
211
|
+
// Render the settings notice shown after the default environment was applied
|
|
212
|
+
// to existing containers: how many were recreated, and how many stopped
|
|
213
|
+
// containers were left for their next start.
|
|
214
|
+
export function renderDefaultEnvSyncNotice(locale, applied, skipped) {
|
|
215
|
+
const containers = (count) => pick(locale, `${count} container${count === 1 ? "" : "s"}`, `${count} 个容器`);
|
|
216
|
+
if (applied === 0 && skipped === 0) {
|
|
217
|
+
return pick(locale, "the default environment is already applied to every running container", "默认环境已应用于所有运行中的容器");
|
|
218
|
+
}
|
|
219
|
+
if (applied === 0) {
|
|
220
|
+
return pick(locale, `no running container needed the default environment; ${containers(skipped)} stopped`, `没有运行中的容器需要默认环境;已跳过 ${containers(skipped)}`);
|
|
221
|
+
}
|
|
222
|
+
const suffix = skipped === 0 ? "" : pick(locale, `; ${containers(skipped)} stopped, to pick up on their next start`, `;已跳过 ${containers(skipped)},将在下次启动时生效`);
|
|
223
|
+
return pick(locale, `applied the default environment to ${containers(applied)}${suffix}`, `已将默认环境应用于 ${containers(applied)}${suffix}`);
|
|
224
|
+
}
|
|
234
225
|
// Render one policy denial.
|
|
235
226
|
export function renderDenial(locale, fact) {
|
|
236
227
|
switch (fact.kind) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { reasonLocale, renderCacheCleanNotice, renderDenial, renderReason, resolveReasonLocale, } from "./approval-reasons.js";
|
|
6
|
+
import { reasonLocale, renderCacheCleanNotice, renderDefaultEnvSyncNotice, renderDenial, renderReason, resolveReasonLocale, } from "./approval-reasons.js";
|
|
7
7
|
import { preExecutePolicy, summarizeArgs } from "./index.js";
|
|
8
8
|
import { testReadSession } from "./test-support.js";
|
|
9
9
|
test("reasonLocale normalizes a locale id to a shipped language", () => {
|
|
@@ -16,16 +16,13 @@ test("reasonLocale normalizes a locale id to a shipped language", () => {
|
|
|
16
16
|
assert.equal(reasonLocale(undefined), "en");
|
|
17
17
|
assert.equal(reasonLocale(42), "en");
|
|
18
18
|
});
|
|
19
|
-
test("resolveReasonLocale
|
|
20
|
-
const settings = (container, locale) => ({
|
|
21
|
-
get: (ns) => (ns === "podman" ? container : ns === "locale" ? locale : undefined),
|
|
22
|
-
});
|
|
19
|
+
test("resolveReasonLocale reads the plugin's recorded locale", () => {
|
|
23
20
|
assert.equal(resolveReasonLocale(undefined), "en");
|
|
24
|
-
assert.equal(resolveReasonLocale(
|
|
25
|
-
assert.equal(resolveReasonLocale(
|
|
26
|
-
assert.equal(resolveReasonLocale(
|
|
27
|
-
assert.equal(resolveReasonLocale(
|
|
28
|
-
assert.equal(resolveReasonLocale(
|
|
21
|
+
assert.equal(resolveReasonLocale(""), "en");
|
|
22
|
+
assert.equal(resolveReasonLocale("zh"), "zh");
|
|
23
|
+
assert.equal(resolveReasonLocale("zh-Hans"), "zh");
|
|
24
|
+
assert.equal(resolveReasonLocale("en"), "en");
|
|
25
|
+
assert.equal(resolveReasonLocale(42), "en");
|
|
29
26
|
});
|
|
30
27
|
test("summarizeArgs renders reasons in Chinese when asked", () => {
|
|
31
28
|
assert.equal(summarizeArgs("container_mount_add", {
|
|
@@ -57,7 +54,12 @@ test("renderDenial renders both languages", () => {
|
|
|
57
54
|
test("renderReason covers the remaining reason kinds in both languages", () => {
|
|
58
55
|
assert.equal(renderReason("en", { kind: "image_rebuild_all" }), "Rebuild all images.");
|
|
59
56
|
assert.equal(renderReason("zh", { kind: "image_rebuild_all" }), "重建全部镜像。");
|
|
60
|
-
assert.equal(renderReason("zh", {
|
|
57
|
+
assert.equal(renderReason("zh", {
|
|
58
|
+
kind: "daemon_start",
|
|
59
|
+
container: "c",
|
|
60
|
+
argv: ["x"],
|
|
61
|
+
uid: 1,
|
|
62
|
+
}), "在容器 “c” 中启动守护进程:x(uid 1)");
|
|
61
63
|
assert.equal(renderReason("zh", {
|
|
62
64
|
kind: "container_bash",
|
|
63
65
|
container: "c",
|
|
@@ -65,11 +67,21 @@ test("renderReason covers the remaining reason kinds in both languages", () => {
|
|
|
65
67
|
uid: 1,
|
|
66
68
|
groups: [3, 4],
|
|
67
69
|
}), "在容器 “c” 中运行 shell 命令:id(uid 1、groups 3、4)");
|
|
68
|
-
assert.equal(renderReason("zh", {
|
|
70
|
+
assert.equal(renderReason("zh", {
|
|
71
|
+
kind: "container_secret_add",
|
|
72
|
+
container: "c",
|
|
73
|
+
secret: "s",
|
|
74
|
+
env: "E",
|
|
75
|
+
}), "将机密 “s” 作为 “E” 注入容器 “c”。");
|
|
69
76
|
assert.equal(renderReason("zh", {
|
|
70
77
|
kind: "container_mount_update",
|
|
71
78
|
container: "valkey-ctr",
|
|
72
|
-
mount: {
|
|
79
|
+
mount: {
|
|
80
|
+
kind: "volume",
|
|
81
|
+
source: "valkey-data",
|
|
82
|
+
destination: "/data",
|
|
83
|
+
readOnly: false,
|
|
84
|
+
},
|
|
73
85
|
}), "更新容器 “valkey-ctr” 中的挂载:将卷 “valkey-data”(挂载到 “/data”)重新挂载为读写。");
|
|
74
86
|
assert.equal(renderReason("zh", {
|
|
75
87
|
kind: "container_mount_update",
|
|
@@ -88,7 +100,12 @@ test("renderReason renders the read-only remount plan", () => {
|
|
|
88
100
|
tool: "bash",
|
|
89
101
|
remount: [
|
|
90
102
|
{ kind: "project", source: "team", readOnly: false },
|
|
91
|
-
{
|
|
103
|
+
{
|
|
104
|
+
kind: "volume",
|
|
105
|
+
source: "data",
|
|
106
|
+
destination: "/data",
|
|
107
|
+
readOnly: false,
|
|
108
|
+
},
|
|
92
109
|
],
|
|
93
110
|
keep: [{ kind: "tmpfs", source: "", destination: "/scratch" }],
|
|
94
111
|
}), 'Read-only mode blocks "bash" while a mount is read-write. Remount these mounts read-only to run it:\n\n- project "team" (read-write)\n- volume "data" at "/data" (read-write)\n\nKept as-is: tmpfs at "/scratch".');
|
|
@@ -103,8 +120,19 @@ test("renderDenial renders the declined remount", () => {
|
|
|
103
120
|
assert.equal(renderDenial("en", { kind: "read_only_remount_declined", tool: "bash" }), 'Denied: read-only mode blocks "bash" and the mounts were not remounted.');
|
|
104
121
|
assert.equal(renderDenial("zh", { kind: "read_only_remount_declined", tool: "bash" }), "已拒绝:只读模式阻止 “bash”,且挂载未被重新挂载。");
|
|
105
122
|
});
|
|
123
|
+
test("renderDefaultEnvSyncNotice renders both languages", () => {
|
|
124
|
+
assert.equal(renderDefaultEnvSyncNotice("en", 2, 0), "applied the default environment to 2 containers");
|
|
125
|
+
assert.equal(renderDefaultEnvSyncNotice("en", 1, 2), "applied the default environment to 1 container; 2 containers stopped, to pick up on their next start");
|
|
126
|
+
assert.equal(renderDefaultEnvSyncNotice("en", 0, 1), "no running container needed the default environment; 1 container stopped");
|
|
127
|
+
assert.equal(renderDefaultEnvSyncNotice("en", 0, 0), "the default environment is already applied to every running container");
|
|
128
|
+
assert.equal(renderDefaultEnvSyncNotice("zh", 2, 0), "已将默认环境应用于 2 个容器");
|
|
129
|
+
assert.equal(renderDefaultEnvSyncNotice("zh", 0, 1), "没有运行中的容器需要默认环境;已跳过 1 个容器");
|
|
130
|
+
});
|
|
106
131
|
test("preExecutePolicy renders a localized deny reason", async () => {
|
|
107
|
-
const denied = (await preExecutePolicy({
|
|
132
|
+
const denied = (await preExecutePolicy({
|
|
133
|
+
name: "container_start",
|
|
134
|
+
agent: { session: { facts: { mode: "read-only" } } },
|
|
135
|
+
}, () => Promise.resolve({ kind: "allow" }), undefined, () => "zh", testReadSession));
|
|
108
136
|
assert.equal(denied.kind, "deny");
|
|
109
137
|
assert.equal(denied.reason, "已拒绝:当前会话为只读,而 “container_start” 需要写入权限。");
|
|
110
138
|
});
|
package/dist/approval.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
import { approvalPath, currentCwd } from "./guest-rpc.js";
|
|
5
|
-
import { inferMountKind, projectMountDestinationReason } from "./mount-input.js";
|
|
5
|
+
import { inferMountKind, projectMountDestinationReason, } from "./mount-input.js";
|
|
6
6
|
import { defaultMountMode } from "./mount-enums.js";
|
|
7
7
|
import { TOOLS } from "./tool-schemas.js";
|
|
8
8
|
import { renderDenial, renderReason, } from "./approval-reasons.js";
|
|
@@ -38,7 +38,9 @@ function mountFact(mount, readOnly) {
|
|
|
38
38
|
// Build the locale-independent reason fact for a gated call, or undefined when
|
|
39
39
|
// the arguments carry too little to describe the call.
|
|
40
40
|
export function reasonFact(name, args, sessionCwd) {
|
|
41
|
-
const str = (key) => typeof args[key] === "string" && args[key] !== ""
|
|
41
|
+
const str = (key) => typeof args[key] === "string" && args[key] !== ""
|
|
42
|
+
? args[key]
|
|
43
|
+
: undefined;
|
|
42
44
|
const listOf = (key) => {
|
|
43
45
|
const value = args[key];
|
|
44
46
|
return Array.isArray(value) && value.length > 0
|
|
@@ -62,14 +64,15 @@ export function reasonFact(name, args, sessionCwd) {
|
|
|
62
64
|
.filter((item) => item !== undefined);
|
|
63
65
|
return items.length === 0 ? undefined : items;
|
|
64
66
|
};
|
|
65
|
-
const
|
|
66
|
-
const value = args
|
|
67
|
+
const mapKeys = (key) => {
|
|
68
|
+
const value = args[key];
|
|
67
69
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
68
70
|
return undefined;
|
|
69
71
|
}
|
|
70
72
|
const keys = Object.keys(value);
|
|
71
73
|
return keys.length === 0 ? undefined : keys;
|
|
72
74
|
};
|
|
75
|
+
const envKeys = () => mapKeys("env");
|
|
73
76
|
const numberListOf = (key) => {
|
|
74
77
|
const value = args[key];
|
|
75
78
|
return Array.isArray(value) && value.length > 0
|
|
@@ -120,6 +123,7 @@ export function reasonFact(name, args, sessionCwd) {
|
|
|
120
123
|
const image = str("image");
|
|
121
124
|
const mounts = mountItems();
|
|
122
125
|
const env = envKeys();
|
|
126
|
+
const secretEnv = mapKeys("secretEnv");
|
|
123
127
|
const paths = listOf("paths");
|
|
124
128
|
return {
|
|
125
129
|
kind: name,
|
|
@@ -127,20 +131,27 @@ export function reasonFact(name, args, sessionCwd) {
|
|
|
127
131
|
...(image === undefined ? {} : { image }),
|
|
128
132
|
...(mounts === undefined ? {} : { mounts }),
|
|
129
133
|
...(env === undefined ? {} : { env }),
|
|
134
|
+
...(secretEnv === undefined ? {} : { secretEnv }),
|
|
130
135
|
...(paths === undefined ? {} : { paths }),
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
case "container_remove": {
|
|
134
139
|
const container = str("container");
|
|
135
|
-
return container === undefined
|
|
140
|
+
return container === undefined
|
|
141
|
+
? undefined
|
|
142
|
+
: { kind: "container_remove", container };
|
|
136
143
|
}
|
|
137
144
|
case "volume_remove": {
|
|
138
145
|
const volume = str("name");
|
|
139
|
-
return volume === undefined
|
|
146
|
+
return volume === undefined
|
|
147
|
+
? undefined
|
|
148
|
+
: { kind: "volume_remove", name: volume };
|
|
140
149
|
}
|
|
141
150
|
case "secret_remove": {
|
|
142
151
|
const secret = str("name");
|
|
143
|
-
return secret === undefined
|
|
152
|
+
return secret === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: { kind: "secret_remove", name: secret };
|
|
144
155
|
}
|
|
145
156
|
case "container_secret_add": {
|
|
146
157
|
const container = str("container");
|
|
@@ -356,7 +367,9 @@ export async function preExecutePolicy(exec, next, getProjectsRoot, getLocale, r
|
|
|
356
367
|
};
|
|
357
368
|
}
|
|
358
369
|
const args = exec.arguments;
|
|
359
|
-
const parsed = typeof args === "object" && args !== null
|
|
370
|
+
const parsed = typeof args === "object" && args !== null
|
|
371
|
+
? args
|
|
372
|
+
: undefined;
|
|
360
373
|
const projectsRoot = getProjectsRoot?.();
|
|
361
374
|
const denyReason = mountDestinationsReason(name, parsed, projectsRoot, locale);
|
|
362
375
|
if (denyReason !== undefined) {
|
|
@@ -366,7 +379,10 @@ export async function preExecutePolicy(exec, next, getProjectsRoot, getLocale, r
|
|
|
366
379
|
return next();
|
|
367
380
|
const sessionCwd = currentCwd(exec);
|
|
368
381
|
if (facts.preset === PODMAN_OPS_PRESET && PODMAN_OPS_APPROVAL_TOOLS.has(name)) {
|
|
369
|
-
return {
|
|
382
|
+
return {
|
|
383
|
+
kind: "ask",
|
|
384
|
+
...askReason(name, parsed ?? {}, sessionCwd, locale),
|
|
385
|
+
};
|
|
370
386
|
}
|
|
371
387
|
return approvalDecision(name, parsed, sessionCwd, locale) ?? next();
|
|
372
388
|
}
|
|
@@ -377,7 +393,8 @@ export async function preExecutePolicy(exec, next, getProjectsRoot, getLocale, r
|
|
|
377
393
|
export function mountDestinationsReason(name, args, projectsRoot, locale = "en") {
|
|
378
394
|
if (args === undefined)
|
|
379
395
|
return undefined;
|
|
380
|
-
if (name === "container_mount_add" || name === "container_mount_remove" ||
|
|
396
|
+
if (name === "container_mount_add" || name === "container_mount_remove" ||
|
|
397
|
+
name === "container_mount_update") {
|
|
381
398
|
return projectMountDestinationReason(projectsRoot, args, locale);
|
|
382
399
|
}
|
|
383
400
|
if (name === "container_start" || name === "container_recreate") {
|