pi-onedev-toolkit 0.2.0 → 0.2.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 +9 -0
- package/package.json +1 -1
- package/src/mutation-approvals.ts +32 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-09-03
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- "Always allow" now saves one project-wide approval (`[server, project, "*"]`) instead of a single operation, so new operations no longer re-prompt after approving. Legacy per-operation entries are still honored.
|
|
8
|
+
- "Allow for this session" also covers every operation in the project for the session.
|
|
9
|
+
- Concurrent identical mutations share one approval dialog instead of opening several.
|
|
10
|
+
- Safer dialog default (session-wide allow) and explicit "ALL OneDev mutations" labels; Enter no longer silently picks "Allow once".
|
|
11
|
+
|
|
3
12
|
## 0.2.0 — 2026-08-26
|
|
4
13
|
|
|
5
14
|
### Security and correctness
|
package/package.json
CHANGED
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
} from "./config-storage.js";
|
|
11
11
|
|
|
12
12
|
const ALLOW_ONCE = "Allow once";
|
|
13
|
-
const ALLOW_SESSION = "Allow
|
|
14
|
-
const ALLOW_SAVED = "Always allow
|
|
13
|
+
const ALLOW_SESSION = "Allow ALL OneDev mutations in this project this session";
|
|
14
|
+
const ALLOW_SAVED = "Always allow ALL OneDev mutations in this project";
|
|
15
15
|
const CANCEL = "Cancel";
|
|
16
16
|
|
|
17
|
+
const ALL_OPERATIONS = "*";
|
|
18
|
+
|
|
17
19
|
export const MUTATION_OPERATIONS = [
|
|
18
20
|
"issue.create",
|
|
19
21
|
"issue.edit",
|
|
@@ -50,13 +52,14 @@ export interface MutationApprovalRequest {
|
|
|
50
52
|
|
|
51
53
|
function scopeKey(
|
|
52
54
|
context: OneDevSessionContext,
|
|
53
|
-
operation: MutationOperation,
|
|
55
|
+
operation: MutationOperation | typeof ALL_OPERATIONS,
|
|
54
56
|
): string {
|
|
55
57
|
return JSON.stringify([context.serverUrl, context.project, operation]);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export class MutationApprovalManager {
|
|
59
61
|
readonly #session = new Set<string>();
|
|
62
|
+
readonly #pending = new Map<string, Promise<void>>();
|
|
60
63
|
|
|
61
64
|
constructor(
|
|
62
65
|
readonly configPath = join(getAgentDir(), "onedev-toolkit.json"),
|
|
@@ -70,6 +73,24 @@ export class MutationApprovalManager {
|
|
|
70
73
|
context: OneDevSessionContext,
|
|
71
74
|
ctx: ExtensionContext,
|
|
72
75
|
request: MutationApprovalRequest,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const key = `${context.serverUrl}\n${context.project}\n${request.operation}`;
|
|
78
|
+
// ponytail: coalesces identical in-flight confirms only; parallel
|
|
79
|
+
// different-operation dialogs still queue on Pi core's single selector
|
|
80
|
+
const inFlight = this.#pending.get(key);
|
|
81
|
+
if (inFlight) return inFlight;
|
|
82
|
+
|
|
83
|
+
const approval = this.#runConfirm(context, ctx, request).finally(() => {
|
|
84
|
+
this.#pending.delete(key);
|
|
85
|
+
});
|
|
86
|
+
this.#pending.set(key, approval);
|
|
87
|
+
return approval;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async #runConfirm(
|
|
91
|
+
context: OneDevSessionContext,
|
|
92
|
+
ctx: ExtensionContext,
|
|
93
|
+
request: MutationApprovalRequest,
|
|
73
94
|
): Promise<void> {
|
|
74
95
|
if (request.signal?.aborted) {
|
|
75
96
|
throw request.signal.reason instanceof Error
|
|
@@ -80,11 +101,13 @@ export class MutationApprovalManager {
|
|
|
80
101
|
throw new Error("OneDev mutation requires a ready server and project context");
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
const
|
|
84
|
-
|
|
104
|
+
const operationScope = scopeKey(context, request.operation);
|
|
105
|
+
const projectScope = scopeKey(context, ALL_OPERATIONS);
|
|
106
|
+
if (this.#session.has(projectScope)) return;
|
|
85
107
|
try {
|
|
86
108
|
const savedScopes = await readAllowedMutationScopes(this.configPath);
|
|
87
|
-
|
|
109
|
+
// legacy per-operation entries saved by older versions stay honored
|
|
110
|
+
if (savedScopes.includes(projectScope) || savedScopes.includes(operationScope)) return;
|
|
88
111
|
} catch (error) {
|
|
89
112
|
if (ctx.hasUI) {
|
|
90
113
|
ctx.ui.notify(
|
|
@@ -107,7 +130,7 @@ export class MutationApprovalManager {
|
|
|
107
130
|
].join("\n");
|
|
108
131
|
const choice = await ctx.ui.select(
|
|
109
132
|
prompt,
|
|
110
|
-
[
|
|
133
|
+
[ALLOW_SESSION, ALLOW_SAVED, ALLOW_ONCE, CANCEL],
|
|
111
134
|
{ signal: request.signal },
|
|
112
135
|
);
|
|
113
136
|
if (!choice || choice === CANCEL) {
|
|
@@ -115,10 +138,10 @@ export class MutationApprovalManager {
|
|
|
115
138
|
}
|
|
116
139
|
if (choice === ALLOW_ONCE) return;
|
|
117
140
|
|
|
118
|
-
this.#session.add(
|
|
141
|
+
this.#session.add(projectScope);
|
|
119
142
|
if (choice === ALLOW_SAVED) {
|
|
120
143
|
try {
|
|
121
|
-
await saveAllowedMutationScope(this.configPath,
|
|
144
|
+
await saveAllowedMutationScope(this.configPath, projectScope);
|
|
122
145
|
} catch (error) {
|
|
123
146
|
ctx.ui.notify(
|
|
124
147
|
`Approval applies to this session only; it could not be saved: ${error instanceof Error ? error.message : String(error)}`,
|