cursor-opencode-provider 0.6.8 → 0.7.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/README.md +51 -181
- package/dist/compaction-marker.d.ts +3 -2
- package/dist/compaction-marker.js +3 -2
- package/dist/host-agent-mode.d.ts +32 -0
- package/dist/host-agent-mode.js +92 -0
- package/dist/language-model.d.ts +15 -1
- package/dist/language-model.js +190 -36
- package/dist/models.d.ts +12 -1
- package/dist/models.js +16 -6
- package/dist/opencode2/catalog.d.ts +46 -11
- package/dist/opencode2/catalog.js +60 -45
- package/dist/opencode2/todo-tools.d.ts +64 -0
- package/dist/opencode2/todo-tools.js +165 -0
- package/dist/opencode2/types.d.ts +138 -83
- package/dist/opencode2/types.js +8 -10
- package/dist/plan-execution-kickoff.d.ts +2 -0
- package/dist/plan-execution-kickoff.js +25 -1
- package/dist/plugin-opencode2.d.ts +4 -1
- package/dist/plugin-opencode2.js +335 -154
- package/dist/plugin.js +5 -1
- package/dist/protocol/conversation-persistence.d.ts +2 -0
- package/dist/protocol/conversation-persistence.js +19 -1
- package/dist/protocol/conversation-state.d.ts +4 -0
- package/dist/protocol/conversation-state.js +4 -0
- package/dist/protocol/switch-mode.d.ts +5 -4
- package/dist/protocol/switch-mode.js +12 -4
- package/dist/protocol/tool-call-bridge.js +1 -1
- package/dist/protocol/tools.d.ts +36 -11
- package/dist/protocol/tools.js +747 -117
- package/dist/protocol/workspace-grounding.d.ts +17 -1
- package/dist/protocol/workspace-grounding.js +34 -5
- package/dist/session.d.ts +7 -1
- package/dist/shared.d.ts +3 -1
- package/dist/shared.js +3 -1
- package/dist/shell-timeout.d.ts +13 -0
- package/dist/shell-timeout.js +38 -0
- package/dist/todo-store.d.ts +29 -0
- package/dist/todo-store.js +78 -0
- package/dist/web-tools.d.ts +13 -0
- package/dist/web-tools.js +83 -5
- package/package.json +6 -3
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runtime duck-type boundary for the OpenCode 2.0 plugin — not host conformance.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* • 2.0 is beta and its schema churns; a hand-maintained subset of only what
|
|
9
|
-
* we touch is more stable than pinning a moving target.
|
|
4
|
+
* Only the methods and fields this plugin calls or publishes. Extra host
|
|
5
|
+
* fields are ignored at runtime. Do not import `@opencode/plugin`: it is not a
|
|
6
|
+
* standalone types surface we can pin next to `@opencode-ai/plugin`, and a host
|
|
7
|
+
* dependency would force a plugin bump on every OpenCode release.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Compile-time checks that our *calls and payloads* still fit the fuller host
|
|
10
|
+
* editor live in `test/opencode2-conformance.types.ts` against
|
|
11
|
+
* `test/opencode2-host-contract.ts`.
|
|
14
12
|
*
|
|
15
13
|
* Effect `Schema` brands (Provider.ID, Model.ID, …) are modelled as plain
|
|
16
14
|
* `string`; brands are compile-time only and erase at runtime.
|
|
@@ -25,17 +23,12 @@ export type ProviderInfo = {
|
|
|
25
23
|
name: string;
|
|
26
24
|
/** `"aisdk:<pkg>"` selects the AI SDK path; a bare specifier selects native. */
|
|
27
25
|
package: string;
|
|
26
|
+
activation: "auto" | "enabled" | "disabled";
|
|
28
27
|
integrationID?: string;
|
|
29
|
-
disabled?: boolean;
|
|
30
|
-
settings?: Record<string, any>;
|
|
31
|
-
headers?: Record<string, string>;
|
|
32
|
-
body?: Record<string, any>;
|
|
33
28
|
};
|
|
34
29
|
export type ModelVariantInfo = {
|
|
35
30
|
id: string;
|
|
36
31
|
settings?: Record<string, any>;
|
|
37
|
-
headers?: Record<string, string>;
|
|
38
|
-
body?: Record<string, any>;
|
|
39
32
|
};
|
|
40
33
|
export type ModelInfo2 = {
|
|
41
34
|
id: string;
|
|
@@ -43,7 +36,6 @@ export type ModelInfo2 = {
|
|
|
43
36
|
modelID: string;
|
|
44
37
|
providerID: string;
|
|
45
38
|
name: string;
|
|
46
|
-
family?: string;
|
|
47
39
|
capabilities: {
|
|
48
40
|
tools: boolean;
|
|
49
41
|
input: readonly string[];
|
|
@@ -66,7 +58,7 @@ export type ModelInfo2 = {
|
|
|
66
58
|
write: number;
|
|
67
59
|
};
|
|
68
60
|
}[];
|
|
69
|
-
status: "
|
|
61
|
+
status: "active";
|
|
70
62
|
enabled: boolean;
|
|
71
63
|
limit: {
|
|
72
64
|
context: number;
|
|
@@ -74,9 +66,6 @@ export type ModelInfo2 = {
|
|
|
74
66
|
output: number;
|
|
75
67
|
};
|
|
76
68
|
settings?: Record<string, any>;
|
|
77
|
-
headers?: Record<string, string>;
|
|
78
|
-
body?: Record<string, any>;
|
|
79
|
-
package?: string;
|
|
80
69
|
};
|
|
81
70
|
export type CredentialOAuth = {
|
|
82
71
|
type: "oauth";
|
|
@@ -101,35 +90,18 @@ export type ConnectionInfo = {
|
|
|
101
90
|
name: string;
|
|
102
91
|
};
|
|
103
92
|
/**
|
|
104
|
-
*
|
|
105
|
-
* host
|
|
106
|
-
* unrelated upstream edits without protecting anything we depend on. The
|
|
107
|
-
* `update` signatures — the ones we actually build the catalog with — stay precise.
|
|
93
|
+
* Inventory writer this plugin uses (`editor.add` only).
|
|
94
|
+
* The host editor is a superset; unused methods are not part of this boundary.
|
|
108
95
|
*/
|
|
109
|
-
export type
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
readonly
|
|
118
|
-
get(providerID: string, modelID: string): any;
|
|
119
|
-
/** Upsert: creates provider and/or model when absent. */
|
|
120
|
-
update(providerID: string, modelID: string, update: (model: ModelInfo2) => void): void;
|
|
121
|
-
remove(providerID: string, modelID: string): void;
|
|
122
|
-
readonly default: {
|
|
123
|
-
get(): {
|
|
124
|
-
providerID: string;
|
|
125
|
-
modelID: string;
|
|
126
|
-
} | undefined;
|
|
127
|
-
set(providerID: string, modelID: string): void;
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
export type CatalogDomain = {
|
|
132
|
-
readonly transform: Transform<CatalogDraft>;
|
|
96
|
+
export type ProviderEditor = {
|
|
97
|
+
add(input: {
|
|
98
|
+
info: ProviderInfo;
|
|
99
|
+
models: readonly ModelInfo2[];
|
|
100
|
+
sourceConnection?: ConnectionInfo;
|
|
101
|
+
}): void;
|
|
102
|
+
};
|
|
103
|
+
export type ProviderDomain = {
|
|
104
|
+
readonly transform: Transform<ProviderEditor>;
|
|
133
105
|
readonly reload: () => Promise<void>;
|
|
134
106
|
};
|
|
135
107
|
export type IntegrationTextPrompt = {
|
|
@@ -142,6 +114,7 @@ export type IntegrationOAuthMethod = {
|
|
|
142
114
|
id: string;
|
|
143
115
|
type: "oauth";
|
|
144
116
|
label: string;
|
|
117
|
+
form?: unknown;
|
|
145
118
|
prompts?: IntegrationTextPrompt[];
|
|
146
119
|
};
|
|
147
120
|
export type IntegrationKeyMethod = {
|
|
@@ -167,7 +140,7 @@ export type IntegrationOAuthAuthorization = {
|
|
|
167
140
|
export type IntegrationOAuthMethodRegistration = {
|
|
168
141
|
readonly integrationID: string;
|
|
169
142
|
readonly method: IntegrationOAuthMethod;
|
|
170
|
-
readonly authorize: (
|
|
143
|
+
readonly authorize: (answer: any) => Promise<IntegrationOAuthAuthorization>;
|
|
171
144
|
readonly refresh?: (credential: CredentialOAuth) => Promise<CredentialOAuth>;
|
|
172
145
|
readonly label?: (credential: CredentialOAuth) => string | undefined;
|
|
173
146
|
};
|
|
@@ -183,14 +156,9 @@ export type IntegrationRef = {
|
|
|
183
156
|
name: string;
|
|
184
157
|
};
|
|
185
158
|
export type IntegrationDraft = {
|
|
186
|
-
list(): readonly any[];
|
|
187
|
-
get(id: string): any;
|
|
188
159
|
update(id: string, update: (integration: IntegrationRef) => void): void;
|
|
189
|
-
remove(id: string): void;
|
|
190
160
|
readonly method: {
|
|
191
|
-
list(integrationID: string): readonly any[];
|
|
192
161
|
update(input: IntegrationMethodRegistration): void;
|
|
193
|
-
remove(integrationID: string, method: any): void;
|
|
194
162
|
};
|
|
195
163
|
};
|
|
196
164
|
export type IntegrationDomain = {
|
|
@@ -201,15 +169,21 @@ export type IntegrationDomain = {
|
|
|
201
169
|
readonly resolve: (connection: ConnectionInfo) => Promise<CredentialValue | undefined>;
|
|
202
170
|
};
|
|
203
171
|
};
|
|
172
|
+
/** Fields this plugin reads from the host model object. */
|
|
173
|
+
export type HostModelRef = {
|
|
174
|
+
readonly id: string;
|
|
175
|
+
readonly modelID: string;
|
|
176
|
+
readonly providerID: string;
|
|
177
|
+
};
|
|
204
178
|
export type AISDKHooks = {
|
|
205
179
|
sdk: {
|
|
206
|
-
readonly model:
|
|
180
|
+
readonly model: HostModelRef;
|
|
207
181
|
readonly package: string;
|
|
208
182
|
readonly options: Record<string, any>;
|
|
209
183
|
sdk?: any;
|
|
210
184
|
};
|
|
211
185
|
language: {
|
|
212
|
-
readonly model:
|
|
186
|
+
readonly model: HostModelRef;
|
|
213
187
|
readonly sdk: any;
|
|
214
188
|
readonly options: Record<string, any>;
|
|
215
189
|
language?: unknown;
|
|
@@ -218,15 +192,37 @@ export type AISDKHooks = {
|
|
|
218
192
|
export type AISDKDomain = {
|
|
219
193
|
readonly hook: Hooks<AISDKHooks>;
|
|
220
194
|
};
|
|
195
|
+
export type ToolOptions = {
|
|
196
|
+
readonly namespace?: string;
|
|
197
|
+
readonly permission?: string;
|
|
198
|
+
readonly codemode?: boolean;
|
|
199
|
+
readonly pinned?: boolean;
|
|
200
|
+
};
|
|
201
|
+
/** Public OpenCode 2 tool context. It deliberately has no permission `ask`. */
|
|
202
|
+
export type ToolExecutionContext = {
|
|
203
|
+
readonly sessionID: string;
|
|
204
|
+
readonly agent: string;
|
|
205
|
+
readonly messageID: string;
|
|
206
|
+
readonly id: string;
|
|
207
|
+
readonly progress: (update: Record<string, unknown>) => Promise<void>;
|
|
208
|
+
};
|
|
221
209
|
export type ToolDefinition = {
|
|
222
210
|
readonly name: string;
|
|
223
211
|
readonly description: string;
|
|
224
212
|
readonly input: unknown;
|
|
225
213
|
readonly output?: unknown;
|
|
226
|
-
readonly
|
|
214
|
+
readonly options?: ToolOptions;
|
|
215
|
+
readonly execute: (input: any, context: ToolExecutionContext) => Promise<any>;
|
|
227
216
|
};
|
|
228
217
|
export type ToolDraft = {
|
|
229
218
|
add(tool: ToolDefinition): void;
|
|
219
|
+
/** Host ToolEditor.get — used to skip registering tools the host already owns. */
|
|
220
|
+
get?(id: string): (ToolDefinition & {
|
|
221
|
+
readonly id?: string;
|
|
222
|
+
}) | undefined;
|
|
223
|
+
list?(): readonly (ToolDefinition & {
|
|
224
|
+
readonly id: string;
|
|
225
|
+
})[];
|
|
230
226
|
};
|
|
231
227
|
export type ToolHookBaseFields = {
|
|
232
228
|
readonly tool: string;
|
|
@@ -257,6 +253,7 @@ export type ToolHooks = {
|
|
|
257
253
|
};
|
|
258
254
|
export type ToolDomain = {
|
|
259
255
|
readonly transform: Transform<ToolDraft>;
|
|
256
|
+
readonly reload: () => Promise<void>;
|
|
260
257
|
readonly hook: Hooks<ToolHooks>;
|
|
261
258
|
};
|
|
262
259
|
export type SessionContext = {
|
|
@@ -273,11 +270,38 @@ export type SessionContext = {
|
|
|
273
270
|
description: string;
|
|
274
271
|
input: unknown;
|
|
275
272
|
}>;
|
|
273
|
+
/** Mutable generation / provider options. OpenCode 2.0 SessionRequest.options. */
|
|
274
|
+
options?: Record<string, unknown>;
|
|
275
|
+
};
|
|
276
|
+
export type SessionCompactionResult = {
|
|
277
|
+
summary: string;
|
|
278
|
+
providerState?: unknown;
|
|
279
|
+
metadata?: Record<string, unknown>;
|
|
280
|
+
tokens?: unknown;
|
|
281
|
+
};
|
|
282
|
+
export type SessionCompaction = SessionContext & {
|
|
283
|
+
result?: SessionCompactionResult;
|
|
284
|
+
};
|
|
285
|
+
export type SessionGenerate = SessionContext;
|
|
286
|
+
export type SessionTitle = {
|
|
287
|
+
readonly sessionID: string;
|
|
288
|
+
readonly model: {
|
|
289
|
+
providerID: string;
|
|
290
|
+
id: string;
|
|
291
|
+
variant?: string;
|
|
292
|
+
};
|
|
293
|
+
system: Array<any>;
|
|
294
|
+
messages: Array<any>;
|
|
295
|
+
options?: Record<string, unknown>;
|
|
296
|
+
result?: string;
|
|
276
297
|
};
|
|
277
298
|
export type SessionHooks = {
|
|
278
299
|
readonly context: SessionContext;
|
|
300
|
+
readonly compaction: SessionCompaction;
|
|
301
|
+
readonly generate: SessionGenerate;
|
|
302
|
+
readonly title: SessionTitle;
|
|
279
303
|
};
|
|
280
|
-
/** Only the `location.directory` field we actually read
|
|
304
|
+
/** Only the `location.directory` field we actually read. */
|
|
281
305
|
export type SessionInfo = {
|
|
282
306
|
readonly id: string;
|
|
283
307
|
readonly location: {
|
|
@@ -289,43 +313,74 @@ export type SessionDomain = {
|
|
|
289
313
|
readonly get: (input: {
|
|
290
314
|
sessionID: string;
|
|
291
315
|
}) => Promise<SessionInfo>;
|
|
316
|
+
readonly switchAgent?: (input: {
|
|
317
|
+
sessionID: string;
|
|
318
|
+
agent: string;
|
|
319
|
+
}) => Promise<void>;
|
|
320
|
+
readonly synthetic?: (input: {
|
|
321
|
+
sessionID: string;
|
|
322
|
+
text: string;
|
|
323
|
+
description?: string;
|
|
324
|
+
metadata?: Record<string, unknown>;
|
|
325
|
+
delivery?: "steer" | "queue";
|
|
326
|
+
resume?: boolean;
|
|
327
|
+
}) => Promise<unknown>;
|
|
328
|
+
readonly prompt?: (input: {
|
|
329
|
+
sessionID: string;
|
|
330
|
+
text: string;
|
|
331
|
+
}) => Promise<unknown>;
|
|
292
332
|
};
|
|
293
|
-
export type
|
|
294
|
-
readonly
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
333
|
+
export type EventDomain = {
|
|
334
|
+
readonly subscribe: (...args: any[]) => any;
|
|
335
|
+
};
|
|
336
|
+
export type ShellCreateBefore = {
|
|
337
|
+
command: string;
|
|
338
|
+
cwd: string;
|
|
339
|
+
timeout: number;
|
|
340
|
+
shell: string;
|
|
341
|
+
env: Record<string, string | undefined>;
|
|
342
|
+
};
|
|
343
|
+
export type ShellDomain = {
|
|
344
|
+
readonly hook: Hooks<{
|
|
345
|
+
readonly "create.before": ShellCreateBefore;
|
|
346
|
+
}>;
|
|
347
|
+
};
|
|
348
|
+
export type WebSearchResult = {
|
|
349
|
+
url: string;
|
|
350
|
+
title?: string;
|
|
351
|
+
content?: string;
|
|
352
|
+
time: {
|
|
353
|
+
published?: number;
|
|
305
354
|
};
|
|
306
355
|
};
|
|
356
|
+
export type WebSearchEditor = {
|
|
357
|
+
add(definition: {
|
|
358
|
+
readonly id: string;
|
|
359
|
+
readonly name: string;
|
|
360
|
+
readonly execute: (input: {
|
|
361
|
+
query: string;
|
|
362
|
+
}, context: {
|
|
363
|
+
readonly signal: AbortSignal;
|
|
364
|
+
}) => Promise<readonly WebSearchResult[]>;
|
|
365
|
+
}): void;
|
|
366
|
+
};
|
|
307
367
|
export type WebSearchDomain = {
|
|
308
|
-
readonly transform: Transform<
|
|
368
|
+
readonly transform: Transform<WebSearchEditor>;
|
|
309
369
|
readonly reload: () => Promise<void>;
|
|
310
370
|
};
|
|
311
|
-
export type
|
|
312
|
-
readonly
|
|
313
|
-
};
|
|
314
|
-
export type App = {
|
|
315
|
-
readonly name: string;
|
|
316
|
-
readonly version: string;
|
|
317
|
-
readonly channel: string;
|
|
371
|
+
export type PluginLocation = {
|
|
372
|
+
readonly directory: string;
|
|
318
373
|
};
|
|
319
374
|
export type PluginContext = {
|
|
320
|
-
readonly app: App;
|
|
321
|
-
readonly options: Readonly<Record<string, any>>;
|
|
322
375
|
readonly aisdk: AISDKDomain;
|
|
323
|
-
readonly catalog: CatalogDomain;
|
|
324
376
|
readonly event: EventDomain;
|
|
325
377
|
readonly integration: IntegrationDomain;
|
|
378
|
+
readonly provider: ProviderDomain;
|
|
326
379
|
readonly session: SessionDomain;
|
|
327
380
|
readonly tool: ToolDomain;
|
|
328
|
-
readonly
|
|
381
|
+
readonly location?: PluginLocation;
|
|
382
|
+
readonly shell?: ShellDomain;
|
|
383
|
+
readonly websearch?: WebSearchDomain;
|
|
329
384
|
};
|
|
330
385
|
export type Cleanup = () => Promise<void> | void;
|
|
331
386
|
export type Plugin2 = {
|
package/dist/opencode2/types.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runtime duck-type boundary for the OpenCode 2.0 plugin — not host conformance.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* • 2.0 is beta and its schema churns; a hand-maintained subset of only what
|
|
9
|
-
* we touch is more stable than pinning a moving target.
|
|
4
|
+
* Only the methods and fields this plugin calls or publishes. Extra host
|
|
5
|
+
* fields are ignored at runtime. Do not import `@opencode/plugin`: it is not a
|
|
6
|
+
* standalone types surface we can pin next to `@opencode-ai/plugin`, and a host
|
|
7
|
+
* dependency would force a plugin bump on every OpenCode release.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Compile-time checks that our *calls and payloads* still fit the fuller host
|
|
10
|
+
* editor live in `test/opencode2-conformance.types.ts` against
|
|
11
|
+
* `test/opencode2-host-contract.ts`.
|
|
14
12
|
*
|
|
15
13
|
* Effect `Schema` brands (Provider.ID, Model.ID, …) are modelled as plain
|
|
16
14
|
* `string`; brands are compile-time only and erase at runtime.
|
|
@@ -37,6 +37,8 @@ export declare function setPlanExecutionKickoff(fn: PlanExecutionKickoffFn | und
|
|
|
37
37
|
export declare function hasPlanExecutionKickoff(): boolean;
|
|
38
38
|
/** Record the approved plan; execution is flushed after the stream settles. */
|
|
39
39
|
export declare function queuePlanExecutionKickoff(input: PlanExecutionKickoffInput): boolean;
|
|
40
|
+
/** Drop any approval state owned by a deleted host session. */
|
|
41
|
+
export declare function cancelPlanExecutionKickoff(sessionID: string | undefined): void;
|
|
40
42
|
/**
|
|
41
43
|
* Run one pending kickoff only after the owning Cursor Run reached a terminal,
|
|
42
44
|
* idle state. Returns true only after successful host handoff.
|
|
@@ -53,9 +53,14 @@ export function planPathFromUri(planUri) {
|
|
|
53
53
|
let kickoff;
|
|
54
54
|
const pending = new Map();
|
|
55
55
|
const warnings = new Map();
|
|
56
|
+
const MAX_PENDING_PLAN_KICKOFFS = 256;
|
|
56
57
|
/** Install (or clear) the host kickoff. */
|
|
57
58
|
export function setPlanExecutionKickoff(fn) {
|
|
58
59
|
kickoff = fn;
|
|
60
|
+
if (!fn) {
|
|
61
|
+
pending.clear();
|
|
62
|
+
warnings.clear();
|
|
63
|
+
}
|
|
59
64
|
}
|
|
60
65
|
export function hasPlanExecutionKickoff() {
|
|
61
66
|
return kickoff !== undefined;
|
|
@@ -77,10 +82,25 @@ export function queuePlanExecutionKickoff(input) {
|
|
|
77
82
|
attempts: 0,
|
|
78
83
|
});
|
|
79
84
|
warnings.delete(sessionID);
|
|
85
|
+
while (pending.size > MAX_PENDING_PLAN_KICKOFFS) {
|
|
86
|
+
const oldest = pending.keys().next().value;
|
|
87
|
+
if (!oldest)
|
|
88
|
+
break;
|
|
89
|
+
pending.delete(oldest);
|
|
90
|
+
warnings.delete(oldest);
|
|
91
|
+
}
|
|
80
92
|
trace(`plan-execution-kickoff: pending sessionID=${sessionID} ` +
|
|
81
93
|
`cursorSessionID=${input.cursorSessionID ?? ""} planPath=${planPath}`);
|
|
82
94
|
return true;
|
|
83
95
|
}
|
|
96
|
+
/** Drop any approval state owned by a deleted host session. */
|
|
97
|
+
export function cancelPlanExecutionKickoff(sessionID) {
|
|
98
|
+
const key = sessionID?.trim();
|
|
99
|
+
if (!key)
|
|
100
|
+
return;
|
|
101
|
+
pending.delete(key);
|
|
102
|
+
warnings.delete(key);
|
|
103
|
+
}
|
|
84
104
|
/**
|
|
85
105
|
* Run one pending kickoff only after the owning Cursor Run reached a terminal,
|
|
86
106
|
* idle state. Returns true only after successful host handoff.
|
|
@@ -100,7 +120,11 @@ export async function flushPlanExecutionKickoff(sessionID, options = {}) {
|
|
|
100
120
|
if (state.cursorSessionID
|
|
101
121
|
&& options.cursorSessionID
|
|
102
122
|
&& state.cursorSessionID !== options.cursorSessionID) {
|
|
103
|
-
|
|
123
|
+
// The owning Run was superseded before the handoff could happen. Applying
|
|
124
|
+
// it from a later Run would execute a stale approval in the wrong turn.
|
|
125
|
+
pending.delete(key);
|
|
126
|
+
warnings.delete(key);
|
|
127
|
+
trace(`plan-execution-kickoff: discarded stale Run sessionID=${key} ` +
|
|
104
128
|
`owner=${state.cursorSessionID} terminal=${options.cursorSessionID}`);
|
|
105
129
|
return false;
|
|
106
130
|
}
|