@meetopenbot/cursor 0.0.1 → 0.0.2
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/index.js +31 -8
- package/dist/session.js +30 -6
- package/dist/state.js +1 -1
- package/dist/stream.js +5 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { agentOutput, definePlugin, shouldHandleInvoke, } from '@meetopenbot/plugin-sdk';
|
|
2
2
|
import { CURSOR_API_KEY_ENV_VAR, resolveConfig, } from './config.js';
|
|
3
3
|
import { formatCursorError } from './format.js';
|
|
4
|
-
import { getOrCreateCursorAgent } from './session.js';
|
|
4
|
+
import { getOrCreateCursorAgent, StaleAgentSessionError } from './session.js';
|
|
5
5
|
import { streamCursorPrompt } from './stream.js';
|
|
6
6
|
export default definePlugin({
|
|
7
7
|
id: 'cursor',
|
|
@@ -107,17 +107,40 @@ export default definePlugin({
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
try {
|
|
110
|
-
|
|
110
|
+
let agentHandle = await getOrCreateCursorAgent({
|
|
111
111
|
config,
|
|
112
112
|
state: handlerCtx.state,
|
|
113
113
|
storage: context.storage,
|
|
114
114
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
try {
|
|
116
|
+
for await (const chunk of streamCursorPrompt(agentHandle.agent, prompt, config, agentHandle.wasResumed)) {
|
|
117
|
+
yield agentOutput({
|
|
118
|
+
agentId: context.agentId,
|
|
119
|
+
content: chunk,
|
|
120
|
+
threadId,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (streamErr) {
|
|
125
|
+
if (streamErr instanceof StaleAgentSessionError) {
|
|
126
|
+
console.warn(`[cursor-plugin] Detected stale resumed agent. Re-creating a fresh agent session...`);
|
|
127
|
+
agentHandle = await getOrCreateCursorAgent({
|
|
128
|
+
config,
|
|
129
|
+
state: handlerCtx.state,
|
|
130
|
+
storage: context.storage,
|
|
131
|
+
forceNew: true,
|
|
132
|
+
});
|
|
133
|
+
for await (const chunk of streamCursorPrompt(agentHandle.agent, prompt, config, agentHandle.wasResumed)) {
|
|
134
|
+
yield agentOutput({
|
|
135
|
+
agentId: context.agentId,
|
|
136
|
+
content: chunk,
|
|
137
|
+
threadId,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
throw streamErr;
|
|
143
|
+
}
|
|
121
144
|
}
|
|
122
145
|
}
|
|
123
146
|
catch (error) {
|
package/dist/session.js
CHANGED
|
@@ -2,6 +2,12 @@ import { Agent, } from '@cursor/sdk';
|
|
|
2
2
|
import { buildModelSelection, } from './config.js';
|
|
3
3
|
import { formatCursorError } from './format.js';
|
|
4
4
|
import { persistCursorState, readPersistedState } from './state.js';
|
|
5
|
+
export class StaleAgentSessionError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'StaleAgentSessionError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
5
11
|
const sessionCache = new Map();
|
|
6
12
|
const buildSessionKey = (state) => state.threadId ? `${state.channelId}:${state.threadId}` : state.channelId;
|
|
7
13
|
const buildAgentOptions = (config) => {
|
|
@@ -44,16 +50,33 @@ const resumeOptions = (config) => ({
|
|
|
44
50
|
const createAgent = async (config) => Agent.create(buildAgentOptions(config));
|
|
45
51
|
const resumeAgent = async (cursorAgentId, config) => Agent.resume(cursorAgentId, resumeOptions(config));
|
|
46
52
|
export const getOrCreateCursorAgent = async (args) => {
|
|
47
|
-
const { config, state, storage } = args;
|
|
53
|
+
const { config, state, storage, forceNew } = args;
|
|
48
54
|
const sessionKey = buildSessionKey(state);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
if (forceNew) {
|
|
56
|
+
const cached = sessionCache.get(sessionKey);
|
|
57
|
+
if (cached) {
|
|
58
|
+
try {
|
|
59
|
+
await cached.agent[Symbol.asyncDispose]();
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
console.warn(`[cursor-plugin] Failed to dispose cached agent:`, e);
|
|
63
|
+
}
|
|
64
|
+
sessionCache.delete(sessionKey);
|
|
65
|
+
}
|
|
66
|
+
await persistCursorState(state, storage, { cursorAgentId: undefined });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const cached = sessionCache.get(sessionKey);
|
|
70
|
+
if (cached)
|
|
71
|
+
return cached;
|
|
72
|
+
}
|
|
73
|
+
const persisted = forceNew ? {} : readPersistedState(state);
|
|
53
74
|
let agent;
|
|
75
|
+
let wasResumed = false;
|
|
54
76
|
if (persisted.cursorAgentId) {
|
|
55
77
|
try {
|
|
56
78
|
agent = await resumeAgent(persisted.cursorAgentId, config);
|
|
79
|
+
wasResumed = true;
|
|
57
80
|
}
|
|
58
81
|
catch (error) {
|
|
59
82
|
console.warn(`[cursor-plugin] Failed to resume ${persisted.cursorAgentId}: ${formatCursorError(error)}`);
|
|
@@ -61,11 +84,12 @@ export const getOrCreateCursorAgent = async (args) => {
|
|
|
61
84
|
}
|
|
62
85
|
if (!agent) {
|
|
63
86
|
agent = await createAgent(config);
|
|
87
|
+
wasResumed = false;
|
|
64
88
|
if (agent.agentId !== persisted.cursorAgentId) {
|
|
65
89
|
await persistCursorState(state, storage, { cursorAgentId: agent.agentId });
|
|
66
90
|
}
|
|
67
91
|
}
|
|
68
|
-
const handle = { agent, sessionKey };
|
|
92
|
+
const handle = { agent, sessionKey, wasResumed };
|
|
69
93
|
sessionCache.set(sessionKey, handle);
|
|
70
94
|
return handle;
|
|
71
95
|
};
|
package/dist/state.js
CHANGED
|
@@ -2,7 +2,7 @@ const asRecord = (value) => value && typeof value === 'object' && !Array.isArray
|
|
|
2
2
|
? value
|
|
3
3
|
: {};
|
|
4
4
|
export const readPersistedState = (state) => {
|
|
5
|
-
const source = state.threadDetails?.state
|
|
5
|
+
const source = state.threadId ? state.threadDetails?.state : state.channelDetails?.state;
|
|
6
6
|
const record = asRecord(source);
|
|
7
7
|
return typeof record.cursorAgentId === 'string'
|
|
8
8
|
? { cursorAgentId: record.cursorAgentId }
|
package/dist/stream.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { buildModelSelection } from './config.js';
|
|
2
2
|
import { createStreamState, formatCursorEvent } from './format.js';
|
|
3
|
+
import { StaleAgentSessionError } from './session.js';
|
|
3
4
|
/** Bridge Cursor SDK run events into an async generator of output chunks. */
|
|
4
|
-
export async function* streamCursorPrompt(agent, prompt, config) {
|
|
5
|
+
export async function* streamCursorPrompt(agent, prompt, config, wasResumed) {
|
|
5
6
|
const state = createStreamState();
|
|
6
7
|
const pending = [];
|
|
7
8
|
let wake;
|
|
@@ -30,6 +31,9 @@ export async function* streamCursorPrompt(agent, prompt, config) {
|
|
|
30
31
|
pending.push(finalText);
|
|
31
32
|
}
|
|
32
33
|
if (result.status === 'error') {
|
|
34
|
+
if (wasResumed && (result.durationMs ?? 0) < 2000) {
|
|
35
|
+
throw new StaleAgentSessionError('Resumed Cursor agent session is stale or expired.');
|
|
36
|
+
}
|
|
33
37
|
const details = JSON.stringify(result);
|
|
34
38
|
pending.push(`**Cursor error:** Run finished with error status. Details: \`${details}\``);
|
|
35
39
|
}
|