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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-onedev-toolkit",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Lightweight OneDev integration for Pi via TOD: repo-aware issue, pull request, build, and watch tools",
5
5
  "type": "module",
6
6
  "author": {
@@ -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 for this session";
14
- const ALLOW_SAVED = "Always allow for this server and project";
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 scope = scopeKey(context, request.operation);
84
- if (this.#session.has(scope)) return;
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
- if (savedScopes.includes(scope)) return;
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
- [ALLOW_ONCE, ALLOW_SESSION, ALLOW_SAVED, CANCEL],
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(scope);
141
+ this.#session.add(projectScope);
119
142
  if (choice === ALLOW_SAVED) {
120
143
  try {
121
- await saveAllowedMutationScope(this.configPath, scope);
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)}`,