@neuralnomads/codenomad-dev 0.18.0-dev-20260628-e8623d31 → 0.18.0-dev-20260709-ca06bd99
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/dist/events/bus.js +4 -0
- package/dist/permissions/auto-accept-manager.js +215 -0
- package/dist/permissions/auto-accept-manager.test.js +528 -0
- package/dist/permissions/auto-accept-store.js +115 -0
- package/dist/permissions/auto-accept-store.test.js +157 -0
- package/dist/permissions/opencode-replier.js +31 -0
- package/dist/server/http-server.js +14 -0
- package/dist/server/routes/yolo.js +12 -0
- package/dist/workspaces/instance-client.js +40 -0
- package/package.json +2 -1
- package/public/assets/{FilesTab-BvAKbOAe.js → FilesTab-4OYnsEpA.js} +2 -2
- package/public/assets/{GitChangesTab-CvmcUbYv.js → GitChangesTab-BRg5IAgC.js} +2 -2
- package/public/assets/{SplitFilePanel-CxDUaPOb.js → SplitFilePanel-CBrsPjMw.js} +1 -1
- package/public/assets/{StatusTab-D5zJNjO2.js → StatusTab-DKKxkw1O.js} +1 -1
- package/public/assets/{align-justify-Bn8CDzlS.js → align-justify-BBbFmxDX.js} +1 -1
- package/public/assets/{bundle-full-DBhFsM8v.js → bundle-full-CaqU_-Bu.js} +1 -1
- package/public/assets/{diff-viewer-CovNS2gz.js → diff-viewer-X5HokaV7.js} +1 -1
- package/public/assets/{index-SO3ktACc.js → index-BVzVj6yO.js} +1 -1
- package/public/assets/{index-D0m4YbHz.js → index-B_mSCVdz.js} +1 -1
- package/public/assets/{index-CGKhj9MH.js → index-BbH6mwer.js} +1 -1
- package/public/assets/{index-D8Us4B7P.js → index-BcHO9P9h.js} +1 -1
- package/public/assets/{index-DtbpjUma.js → index-CNR6iBgw.js} +1 -1
- package/public/assets/{index-DlKCavfp.js → index-Cb1cfkUb.js} +1 -1
- package/public/assets/{index-byf9BI5h.js → index-DHYmm6_R.js} +2 -2
- package/public/assets/{index-CWut2MH1.js → index-DcfoDBMN.js} +1 -1
- package/public/assets/{index-Df02kJ_l.js → index-DqvdokW8.js} +1 -1
- package/public/assets/{index-coIdctUe.js → index-cfYxASJ7.js} +1 -1
- package/public/assets/{index-Dwb5ls4Z.js → index-jd2JD4UX.js} +1 -1
- package/public/assets/{loading-BitVzHui.js → loading-Bm_4ihk-.js} +1 -1
- package/public/assets/main-BYOHwhSs.js +68 -0
- package/public/assets/{markdown-0328GCk-.js → markdown-AEVUKCN6.js} +3 -3
- package/public/assets/{monaco-viewer-q26M2dw6.js → monaco-viewer-B3Jl6a5p.js} +3 -3
- package/public/assets/{tool-call-B8_OPv1H.js → tool-call-Bp55vSts.js} +7 -7
- package/public/assets/{unified-picker-bwjTqCPs.js → unified-picker-_ya4P-Mo.js} +1 -1
- package/public/assets/{wrap-text-DnuQ9rVl.js → wrap-text-Clu8BV6B.js} +1 -1
- package/public/index.html +3 -3
- package/public/loading.html +3 -3
- package/public/sw.js +1 -1
- package/public/assets/main-DMQeFbbj.js +0 -68
package/dist/events/bus.js
CHANGED
|
@@ -27,6 +27,8 @@ export class EventBus extends EventEmitter {
|
|
|
27
27
|
this.on("instance.dataChanged", handler);
|
|
28
28
|
this.on("instance.event", handler);
|
|
29
29
|
this.on("instance.eventStatus", handler);
|
|
30
|
+
this.on("yolo.stateChanged", handler);
|
|
31
|
+
this.on("yolo.autoAccepted", handler);
|
|
30
32
|
return () => {
|
|
31
33
|
this.off("workspace.created", handler);
|
|
32
34
|
this.off("workspace.started", handler);
|
|
@@ -40,6 +42,8 @@ export class EventBus extends EventEmitter {
|
|
|
40
42
|
this.off("instance.dataChanged", handler);
|
|
41
43
|
this.off("instance.event", handler);
|
|
42
44
|
this.off("instance.eventStatus", handler);
|
|
45
|
+
this.off("yolo.stateChanged", handler);
|
|
46
|
+
this.off("yolo.autoAccepted", handler);
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { AutoAcceptStore } from "./auto-accept-store";
|
|
2
|
+
const PERMISSION_ASK_TYPES = new Set(["permission.v2.asked", "permission.asked", "permission.updated"]);
|
|
3
|
+
const PERMISSION_REPLIED_TYPES = new Set(["permission.v2.replied", "permission.replied"]);
|
|
4
|
+
const SESSION_UPSERT_TYPES = new Set(["session.updated", "session.created"]);
|
|
5
|
+
const SESSION_REMOVE_TYPES = new Set(["session.deleted"]);
|
|
6
|
+
export class AutoAcceptManager {
|
|
7
|
+
constructor(deps) {
|
|
8
|
+
this.deps = deps;
|
|
9
|
+
this.store = new AutoAcceptStore();
|
|
10
|
+
/** instanceId:permissionId entries currently being replied, to dedupe re-emissions */
|
|
11
|
+
this.inFlight = new Set();
|
|
12
|
+
/** instanceId -> (permissionId -> pending permission) awaiting a reply */
|
|
13
|
+
this.pending = new Map();
|
|
14
|
+
/** instanceId:permissionId -> failure count, to stop retrying stuck permissions */
|
|
15
|
+
this.replyAttempts = new Map();
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
if (this.unsubscribe)
|
|
19
|
+
return;
|
|
20
|
+
const handler = (payload) => {
|
|
21
|
+
if (!payload || !payload.instanceId || !payload.event)
|
|
22
|
+
return;
|
|
23
|
+
this.handleInstanceEvent(payload.instanceId, payload.event);
|
|
24
|
+
};
|
|
25
|
+
const onStopped = (event) => {
|
|
26
|
+
if (event?.workspaceId)
|
|
27
|
+
this.clearInstance(event.workspaceId);
|
|
28
|
+
};
|
|
29
|
+
const onError = (event) => {
|
|
30
|
+
if (event?.workspace?.id)
|
|
31
|
+
this.clearInstance(event.workspace.id);
|
|
32
|
+
};
|
|
33
|
+
this.deps.eventBus.on("instance.event", handler);
|
|
34
|
+
this.deps.eventBus.on("workspace.stopped", onStopped);
|
|
35
|
+
this.deps.eventBus.on("workspace.error", onError);
|
|
36
|
+
this.unsubscribe = () => {
|
|
37
|
+
this.deps.eventBus.off("instance.event", handler);
|
|
38
|
+
this.deps.eventBus.off("workspace.stopped", onStopped);
|
|
39
|
+
this.deps.eventBus.off("workspace.error", onError);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
stop() {
|
|
43
|
+
this.unsubscribe?.();
|
|
44
|
+
this.unsubscribe = undefined;
|
|
45
|
+
}
|
|
46
|
+
isEnabled(instanceId, sessionId) {
|
|
47
|
+
return this.store.isEnabled(instanceId, sessionId);
|
|
48
|
+
}
|
|
49
|
+
toggle(instanceId, sessionId) {
|
|
50
|
+
const enabled = this.store.toggle(instanceId, sessionId);
|
|
51
|
+
this.deps.eventBus.publish({ type: "yolo.stateChanged", instanceId, sessionId, enabled });
|
|
52
|
+
if (enabled) {
|
|
53
|
+
this.drainPending(instanceId, sessionId);
|
|
54
|
+
}
|
|
55
|
+
return enabled;
|
|
56
|
+
}
|
|
57
|
+
clearInstance(instanceId) {
|
|
58
|
+
this.store.clearInstance(instanceId);
|
|
59
|
+
this.pending.delete(instanceId);
|
|
60
|
+
const prefix = `${instanceId}:`;
|
|
61
|
+
for (const key of Array.from(this.inFlight.keys())) {
|
|
62
|
+
if (key.startsWith(prefix))
|
|
63
|
+
this.inFlight.delete(key);
|
|
64
|
+
}
|
|
65
|
+
for (const key of Array.from(this.replyAttempts.keys())) {
|
|
66
|
+
if (key.startsWith(prefix))
|
|
67
|
+
this.replyAttempts.delete(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
handleInstanceEvent(instanceId, event) {
|
|
71
|
+
if (!event || typeof event.type !== "string")
|
|
72
|
+
return;
|
|
73
|
+
if (SESSION_UPSERT_TYPES.has(event.type)) {
|
|
74
|
+
this.ingestSession(instanceId, event.properties);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (SESSION_REMOVE_TYPES.has(event.type)) {
|
|
78
|
+
const info = event.properties?.info;
|
|
79
|
+
const id = readString(info?.id) ?? readString(event.properties?.id);
|
|
80
|
+
if (id) {
|
|
81
|
+
this.store.removeSession(instanceId, id);
|
|
82
|
+
this.removePendingForSession(instanceId, id);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (PERMISSION_REPLIED_TYPES.has(event.type)) {
|
|
87
|
+
this.handlePermissionReplied(instanceId, event.properties);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (PERMISSION_ASK_TYPES.has(event.type)) {
|
|
91
|
+
this.handlePermissionRequest(instanceId, event.type, event.properties);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
ingestSession(instanceId, properties) {
|
|
95
|
+
// OpenCode wraps session records under `properties.info` for
|
|
96
|
+
// session.created/updated/deleted (see SDK EventSessionUpdated). Accept a
|
|
97
|
+
// flat fallback only for defensive compatibility.
|
|
98
|
+
const info = properties;
|
|
99
|
+
const session = (info && typeof info === "object" && "info" in info ? info.info : info);
|
|
100
|
+
if (!session || typeof session.id !== "string")
|
|
101
|
+
return;
|
|
102
|
+
const parentId = session.parentID ?? session.parentId ?? null;
|
|
103
|
+
const revert = session.revert ?? undefined;
|
|
104
|
+
this.store.upsertSession(instanceId, { id: session.id, parentId, revert });
|
|
105
|
+
// Session ancestry may have changed (parent discovered, revert toggled).
|
|
106
|
+
// Re-drain pending permissions whose family root may have migrated into
|
|
107
|
+
// an enabled family — mirrors the old UI's drainAutoAcceptPermissions-
|
|
108
|
+
// ForInstance trigger on session.updated (#497).
|
|
109
|
+
this.drainPending(instanceId, session.id);
|
|
110
|
+
}
|
|
111
|
+
handlePermissionRequest(instanceId, eventType, permission) {
|
|
112
|
+
const request = permission;
|
|
113
|
+
if (!request)
|
|
114
|
+
return;
|
|
115
|
+
const permissionId = readString(request.id);
|
|
116
|
+
const sessionId = readString(request.sessionID) ?? readString(request.sessionId);
|
|
117
|
+
if (!permissionId || !sessionId)
|
|
118
|
+
return;
|
|
119
|
+
// Infer source from the event type, but prefer the already-tracked source
|
|
120
|
+
// for permission.updated (which may belong to a v2 permission).
|
|
121
|
+
const existing = this.pending.get(instanceId)?.get(permissionId);
|
|
122
|
+
const source = eventType === "permission.v2.asked" ? "v2" : (existing?.source ?? "legacy");
|
|
123
|
+
// `permission.updated` represents a detail change for a permission that
|
|
124
|
+
// is *already* pending. If it is no longer in our pending set it was
|
|
125
|
+
// already replied to (by us or the user) — skip to avoid a duplicate reply.
|
|
126
|
+
if (eventType === "permission.updated" && !this.pending.get(instanceId)?.has(permissionId)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.addPending(instanceId, { permissionId, sessionId, source });
|
|
130
|
+
if (!this.store.isEnabled(instanceId, sessionId))
|
|
131
|
+
return;
|
|
132
|
+
this.tryAutoAccept(instanceId, permissionId, sessionId, source);
|
|
133
|
+
}
|
|
134
|
+
handlePermissionReplied(instanceId, properties) {
|
|
135
|
+
const request = properties ?? {};
|
|
136
|
+
const permissionId = readString(request.id) ??
|
|
137
|
+
readString(request.requestID) ??
|
|
138
|
+
readString(request.permissionID) ??
|
|
139
|
+
readString(request.requestId) ??
|
|
140
|
+
readString(request.permissionId);
|
|
141
|
+
if (permissionId)
|
|
142
|
+
this.removePending(instanceId, permissionId);
|
|
143
|
+
}
|
|
144
|
+
tryAutoAccept(instanceId, permissionId, sessionId, source) {
|
|
145
|
+
const key = `${instanceId}:${permissionId}`;
|
|
146
|
+
if (this.inFlight.has(key))
|
|
147
|
+
return;
|
|
148
|
+
const attempts = this.replyAttempts.get(key) ?? 0;
|
|
149
|
+
if (attempts >= AutoAcceptManager.MAX_REPLY_ATTEMPTS)
|
|
150
|
+
return;
|
|
151
|
+
this.inFlight.add(key);
|
|
152
|
+
this.replyAttempts.set(key, attempts + 1);
|
|
153
|
+
const reply = { instanceId, permissionId, sessionId, source, reply: "once" };
|
|
154
|
+
void this.deps.replier(reply)
|
|
155
|
+
.then(() => {
|
|
156
|
+
this.replyAttempts.delete(key);
|
|
157
|
+
this.removePending(instanceId, permissionId);
|
|
158
|
+
this.deps.eventBus.publish({ type: "yolo.autoAccepted", instanceId, sessionId, permissionId });
|
|
159
|
+
})
|
|
160
|
+
.catch((error) => {
|
|
161
|
+
this.deps.logger.error({ instanceId, permissionId, err: error, attempt: attempts + 1 }, "Yolo auto-accept reply failed");
|
|
162
|
+
if (attempts + 1 >= AutoAcceptManager.MAX_REPLY_ATTEMPTS) {
|
|
163
|
+
this.removePending(instanceId, permissionId);
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
.finally(() => {
|
|
167
|
+
this.inFlight.delete(key);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** Auto-accept all pending permissions belonging to the same family root. */
|
|
171
|
+
drainPending(instanceId, sessionId) {
|
|
172
|
+
const instancePending = this.pending.get(instanceId);
|
|
173
|
+
if (!instancePending || instancePending.size === 0)
|
|
174
|
+
return;
|
|
175
|
+
const root = this.store.familyRoot(instanceId, sessionId);
|
|
176
|
+
for (const entry of Array.from(instancePending.values())) {
|
|
177
|
+
if (this.store.familyRoot(instanceId, entry.sessionId) === root) {
|
|
178
|
+
this.tryAutoAccept(instanceId, entry.permissionId, entry.sessionId, entry.source);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
addPending(instanceId, entry) {
|
|
183
|
+
let instancePending = this.pending.get(instanceId);
|
|
184
|
+
if (!instancePending) {
|
|
185
|
+
instancePending = new Map();
|
|
186
|
+
this.pending.set(instanceId, instancePending);
|
|
187
|
+
}
|
|
188
|
+
instancePending.set(entry.permissionId, entry);
|
|
189
|
+
}
|
|
190
|
+
removePending(instanceId, permissionId) {
|
|
191
|
+
const instancePending = this.pending.get(instanceId);
|
|
192
|
+
if (instancePending?.delete(permissionId)) {
|
|
193
|
+
this.replyAttempts.delete(`${instanceId}:${permissionId}`);
|
|
194
|
+
if (instancePending.size === 0)
|
|
195
|
+
this.pending.delete(instanceId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
removePendingForSession(instanceId, sessionId) {
|
|
199
|
+
const instancePending = this.pending.get(instanceId);
|
|
200
|
+
if (!instancePending)
|
|
201
|
+
return;
|
|
202
|
+
for (const [permId, entry] of Array.from(instancePending)) {
|
|
203
|
+
if (entry.sessionId === sessionId) {
|
|
204
|
+
instancePending.delete(permId);
|
|
205
|
+
this.replyAttempts.delete(`${instanceId}:${permId}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (instancePending.size === 0)
|
|
209
|
+
this.pending.delete(instanceId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
AutoAcceptManager.MAX_REPLY_ATTEMPTS = 3;
|
|
213
|
+
function readString(value) {
|
|
214
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
215
|
+
}
|