@magpie-community/opencode-cursor-auth 0.2.34 → 0.2.36

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 CHANGED
@@ -123,8 +123,11 @@ caller's tool results is bounded by the 120 seconds above instead.
123
123
 
124
124
  Cursor's count of a step's tool calls includes calls the plugin answers itself:
125
125
  a tool the request doesn't have is refused to the model, and Cursor's own
126
- `get_mcp_tools` is answered with the request's tools. The step's calls are the
127
- rest.
126
+ `get_mcp_tools` is answered with the request's tools. It also includes calls
127
+ Cursor completes by itself, which it writes and completes in the step but never
128
+ starts on the client or sends to it; those count toward it too. Once the count
129
+ has come, a checkpoint whose state waits on exactly the calls the caller was
130
+ given also ends the step (#550). The step's calls are the rest.
128
131
 
129
132
  These are retention limits. A missing, expired, restarted, busy, incompatible,
130
133
  or full owner uses the original history reconstruction and stateless decoder
@@ -312,7 +315,10 @@ portable data folder. The newest 20 are kept. A file holds the Run's
312
315
  frames in order, both ways, as each frame's time (ms from the Run's
313
316
  start), size, kind (text, thinking, checkpoint, heartbeat, ...), field
314
317
  numbers and sizes, and the field numbers and sizes one level inside them,
315
- plus the error and the model id. No prompt, reply or thinking text, tool
318
+ plus the error and the model id. A few of Cursor's numbers are kept too: a
319
+ tool count's value, which kind of call (the field number of Cursor's tool
320
+ call) a call update is about, how many calls a checkpoint waits on, and,
321
+ for a Run that went without the native session, why. No prompt, reply or thinking text, tool
316
322
  name or argument, blob or checkpoint content, token, header, account or
317
323
  conversation id is written; the error's message has anything shaped like
318
324
  an email, id or token taken out. Look the file over before you attach it.
package/index.mjs CHANGED
@@ -28,7 +28,7 @@
28
28
  //
29
29
  // OpenCode speaks chat completions to it (@ai-sdk/openai-compatible); the
30
30
  // fetch here answers them.
31
- import { NATIVE_LIMITS, nativeSessions, NativeSessionError, nativeFields, nativeMcpArgs, Quiet, setWireTrace } from "./native-session.mjs"
31
+ import { NATIVE_LIMITS, nativeSessions, NativeSessionError, nativeFields, nativeMcpArgs, pendingCalls, Quiet, setWireTrace, StepCalls } from "./native-session.mjs"
32
32
  import { isDeepStrictEqual } from "node:util"
33
33
  import http2 from "node:http2"
34
34
  import { STATUS_CODES } from "node:http"
@@ -2747,7 +2747,7 @@ async function runNative({ tok, base, id, tools, conv, convID, signal, maxMode,
2747
2747
 
2748
2748
  // runOnce is one Run of the chat on the agent API at base: an async
2749
2749
  // iterator of the answer's pieces, or the error it failed with before any.
2750
- async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, params }) {
2750
+ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, params, why }) {
2751
2751
  // the Run's conversation_id, a new one when nothing names the session:
2752
2752
  // its usage event is found by it
2753
2753
  const cid = convID || randomUUID()
@@ -2778,7 +2778,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
2778
2778
  let closed = false
2779
2779
  let heartbeat
2780
2780
  // a Run Cursor stops answering ends, as a retained one does (#2672)
2781
- const quiet = new Quiet(NATIVE_LIMITS, () => close(), { path: "stateless", model: id })
2781
+ const quiet = new Quiet(NATIVE_LIMITS, () => close(), { path: "stateless", model: id, why })
2782
2782
  const send = (msg) => {
2783
2783
  if (!closed && !req.destroyed && req.writable) {
2784
2784
  quiet.trace?.frame("out", msg)
@@ -2822,6 +2822,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
2822
2822
  close,
2823
2823
  stepUsage: book.own,
2824
2824
  owedUsage: book.owed,
2825
+ note: (name) => quiet.trace?.event(name),
2825
2826
  })
2826
2827
  const wrapped = (async function* () {
2827
2828
  try {
@@ -2852,7 +2853,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
2852
2853
 
2853
2854
  // decode follows the server's messages into the answer's pieces until the
2854
2855
  // turn ends, or the model has made its tool calls.
2855
- async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage, owedUsage }) {
2856
+ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage, owedUsage, note, countWait = NATIVE_LIMITS.countWait }) {
2856
2857
  let calls = 0
2857
2858
  // answered: the step's calls answered here (a refusal, the MCP tool
2858
2859
  // list), which Cursor's count of the step's calls takes in too (#142)
@@ -2860,14 +2861,18 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2860
2861
  // ended: Cursor said what the turn used
2861
2862
  let ended = false
2862
2863
  let listed = 0
2864
+ // what Cursor said of the step's calls: those it completed by itself
2865
+ // count toward listed too, and never come as an exec (plugins#550)
2866
+ const step = new StepCalls(fields)
2863
2867
  // stepDone is whether every call the step listed has come. A step whose
2864
2868
  // calls were all answered here goes on on Cursor's side, under a count of
2865
2869
  // its own.
2866
2870
  const stepDone = () => {
2867
- if (!listed || calls + answered < listed) return false
2871
+ if (!listed || calls + answered + step.completed < listed) return false
2868
2872
  if (calls > 0) return true
2869
2873
  listed = 0
2870
2874
  answered = 0
2875
+ step.nextStep()
2871
2876
  return false
2872
2877
  }
2873
2878
  let said = 0
@@ -2932,8 +2937,48 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2932
2937
  return finish(owedUsage ? await owedUsage(calls === 0).catch(() => null) : null)
2933
2938
  }
2934
2939
  const closeExec = (id) => send(pb().bytes(5, pb().bytes(1, pb().varint(1, id))).done())
2940
+ // A step whose calls came and whose count never does: Cursor sent the
2941
+ // calls and then only heartbeats (plugins#550, the fifth trace: two
2942
+ // calls, no count, 240 s of heartbeats until quiet). Once countWait has
2943
+ // passed since Cursor's last frame that wasn't a heartbeat, with no call
2944
+ // still being written, the calls that came are the step's. Heartbeats
2945
+ // never move the deadline: they are all such a Run sends.
2946
+ const it = (stream[Symbol.asyncIterator] ?? stream[Symbol.iterator]).call(stream)
2947
+ let heardAt = Date.now()
2948
+ const read = async () => {
2949
+ const next = Promise.resolve(it.next())
2950
+ if (!calls || listed || step.writing) return next
2951
+ next.catch(() => {}) // a read left behind by the cut fails unheard
2952
+ let timer
2953
+ const cut = new Promise((r) => {
2954
+ timer = setTimeout(() => r({ cut: true }), Math.max(0, heardAt + countWait - Date.now()))
2955
+ timer.unref?.()
2956
+ })
2957
+ try {
2958
+ return await Promise.race([next, cut])
2959
+ } finally {
2960
+ clearTimeout(timer)
2961
+ }
2962
+ }
2963
+ const beat = (data) => {
2964
+ try {
2965
+ const ms = fields(data)
2966
+ return ms.length > 0 && ms.every((m) => m.num === 1 && fields(m.data).every((u) => u.num === 13))
2967
+ } catch {
2968
+ return false
2969
+ }
2970
+ }
2935
2971
  try {
2936
- for await (const f of stream) {
2972
+ for (;;) {
2973
+ const r = await read()
2974
+ if (r.cut) {
2975
+ note?.("no count")
2976
+ yield await settled()
2977
+ return
2978
+ }
2979
+ if (r.done) break
2980
+ const f = r.value
2981
+ if (f.end || !beat(f.data)) heardAt = Date.now()
2937
2982
  if (f.end) {
2938
2983
  const e = failure(200, f.data)
2939
2984
  if (e.status < 200 || e.status > 299) yield { error: e }
@@ -2984,6 +3029,16 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2984
3029
  return
2985
3030
  }
2986
3031
  break
3032
+ case 2: // a call started on the client
3033
+ case 3: // a call completed: by Cursor itself when it never started here
3034
+ case 7: // a call the model is writing
3035
+ // still named in an empty reply's list: only their ids are read
3036
+ count(seen.updates, u.num)
3037
+ if (step.update(u.num, uf) && (note?.("completed by Cursor"), stepDone())) {
3038
+ yield await settled()
3039
+ return
3040
+ }
3041
+ break
2987
3042
  default:
2988
3043
  count(seen.updates, u.num)
2989
3044
  }
@@ -2991,6 +3046,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2991
3046
  break
2992
3047
  case 2: {
2993
3048
  // exec_server_message: the server asks the client to run something
3049
+ step.exec(m.data)
2994
3050
  const call = exec(m.data, tools, send, closeExec, seen.refused)
2995
3051
  if (call) {
2996
3052
  said += call.args.length
@@ -3003,6 +3059,18 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
3003
3059
  }
3004
3060
  break
3005
3061
  }
3062
+ case 3:
3063
+ // conversation_checkpoint_update: Cursor's state. Once the step
3064
+ // has its count, a state waiting on just the calls handed to the
3065
+ // caller is a step Cursor has finished: what more the count
3066
+ // says will not come (plugins#550)
3067
+ if (listed && calls > 0 && pendingCalls(m.data, fields) === calls) {
3068
+ note?.("waited on")
3069
+ yield await settled()
3070
+ return
3071
+ }
3072
+ count(seen.messages, m.num)
3073
+ break
3006
3074
  case 4: {
3007
3075
  // kv_server_message: a blob wanted, or one to keep
3008
3076
  const kv = fields(m.data)
@@ -3033,7 +3101,10 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
3033
3101
  } catch (e) {
3034
3102
  // gone quiet is no answer, calls or not: the step's count never came
3035
3103
  if (e?.quiet) {
3036
- yield { error: { status: e.status, message: e.message } }
3104
+ // with what came of the step's calls, as the native path's batch
3105
+ // error says it (plugins#550)
3106
+ const batch = calls || listed ? `; the step's tool count ${listed || "not sent"}: ${calls} calls came, ${answered} answered here, ${step.completed} completed by Cursor` : ""
3107
+ yield { error: { status: e.status, message: batch ? e.message.replace(/( \(try again\))?$/, batch + "$1") : e.message } }
3037
3108
  return
3038
3109
  }
3039
3110
  if (calls > 0 && /EOF/.test(e?.message ?? "")) {
@@ -3041,6 +3112,9 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
3041
3112
  return
3042
3113
  }
3043
3114
  yield { error: { status: 502, message: "the reply broke off: " + (e?.message ?? e) } }
3115
+ } finally {
3116
+ // not waited for: after a cut, the read left behind ends with the Run
3117
+ Promise.resolve(it.return?.()).catch(() => {})
3044
3118
  }
3045
3119
  }
3046
3120
 
@@ -3207,10 +3281,11 @@ async function answerOf(auth, chat, signal, session = "") {
3207
3281
  // uses the original history builder/decoder without cancelling a live parent.
3208
3282
  const native = prepared.fallback ? null : nativeSessions.create(prepared, { id, maxMode, params }, { pb, exec, usageOf, failure })
3209
3283
  const args = { tok, base, id, tools, conv, convID, signal, maxMode, params }
3210
- let response = native ? await runNative({ ...args, native }) : await runOnce(args)
3284
+ let response = native ? await runNative({ ...args, native }) : await runOnce({ ...args, why: prepared.fallback || "retention capacity or reservation unavailable" })
3211
3285
  const rebuild = async () => {
3212
- const fallback = await runOnce(args)
3213
- fallback.diagnostic = "stateless; bootstrap=imported-history; reason=" + (response.error?.continuityReason || "missing-batch-count")
3286
+ const why = response.error?.continuityReason || "missing-batch-count"
3287
+ const fallback = await runOnce({ ...args, why })
3288
+ fallback.diagnostic = "stateless; bootstrap=imported-history; reason=" + why
3214
3289
  return fallback
3215
3290
  }
3216
3291
  // A missing or conflicting native batch count may be retried once,
@@ -9,6 +9,12 @@ export const NATIVE_LIMITS = {
9
9
  resultWait: 120_000,
10
10
  lifetime: 15 * 60_000,
11
11
  batchWait: 30_000,
12
+ // How long a stateless step whose calls came waits for Cursor's count of
13
+ // them (update 27) with nothing but heartbeats before it hands the calls
14
+ // that came to the caller (plugins#550). A count comes within ms of the
15
+ // step's last call; a Run that sent calls and then only heartbeats has
16
+ // stopped, and the caller's next request goes on from the calls' results.
17
+ countWait: 60_000,
12
18
  drain: 6000,
13
19
  replayWait: 120_000,
14
20
  blobBytes: 64 << 20,
@@ -55,7 +61,13 @@ const failureOf = (e) => ({ status: e?.status ?? 502, message: e?.message ?? Str
55
61
 
56
62
  // kindOf is what a frame of AgentServerMessage is, for Quiet: a heartbeat
57
63
  // alone, or what an error names as the last thing Cursor sent
58
- const UPDATES = { 1: "text", 4: "thinking", 14: "turn end", 27: "tool count" }
64
+ // (names after cursor-agent's agent.v1 InteractionUpdate, 2026.10.01)
65
+ const UPDATES = {
66
+ 1: "text", 2: "tool call started", 3: "tool call completed", 4: "thinking", 5: "thinking done",
67
+ 6: "user message", 7: "partial tool call", 8: "token count", 9: "summary", 10: "summary started",
68
+ 11: "summary done", 12: "shell output", 14: "turn end", 15: "tool call delta", 16: "step started",
69
+ 17: "step done", 27: "tool count",
70
+ }
59
71
  const MESSAGES = { 2: "tool call", 3: "checkpoint", 4: "blob request" }
60
72
  function kindOf(f) {
61
73
  if (f.end) return "stream end"
@@ -249,6 +261,26 @@ export function scrub(s) {
249
261
  .replace(/[A-Za-z0-9_\-+/=.]{24,}/g, "<redacted>")
250
262
  .slice(0, 2000)
251
263
  }
264
+ // detail is what a trace keeps of an inbound frame past its shape, all of
265
+ // it numbers of Cursor's protocol, never content (plugins#550): a tool
266
+ // count's number, the kind of call (ToolCall's field) a call update is
267
+ // of, and how many calls a checkpoint's state waits on
268
+ function detail(data) {
269
+ const out = {}
270
+ for (const m of nativeFields(data)) {
271
+ if (m.num === 1) {
272
+ for (const u of nativeFields(m.data)) {
273
+ if (u.num === 27) out.count = num(nativeFields(u.data), 1)
274
+ else if (u.num === 2 || u.num === 3 || u.num === 7) {
275
+ const tc = field(nativeFields(u.data), 2)
276
+ const kind = tc && nativeFields(tc.data)[0]
277
+ if (kind) out.tool = kind.wire === 2 ? [kind.num, nativeFields(kind.data)[0]?.num ?? 0] : [kind.num]
278
+ }
279
+ }
280
+ } else if (m.num === 3) out.pending = pendingCalls(m.data)
281
+ }
282
+ return out
283
+ }
252
284
  export class WireTrace {
253
285
  static open(meta) {
254
286
  let dir = ""
@@ -258,6 +290,8 @@ export class WireTrace {
258
290
  constructor(dir, meta = {}) {
259
291
  this.dir = dir
260
292
  this.meta = { path: String(meta.path ?? ""), model: String(meta.model ?? "") }
293
+ // why a Run went stateless: the retention fallback's reason
294
+ if (meta.why) this.meta.why = String(meta.why)
261
295
  this.started = Date.now()
262
296
  this.head = []
263
297
  this.tail = []
@@ -281,6 +315,7 @@ export class WireTrace {
281
315
  if (dir === "in") entry.kind = kind ?? "heartbeat"
282
316
  if (dir === "in" && f.end) entry.end = true
283
317
  else try { entry.fields = shape(data, true) } catch { entry.malformed = true }
318
+ if (dir === "in" && !f.end) try { Object.assign(entry, detail(data)) } catch {}
284
319
  this.push(entry)
285
320
  }
286
321
  event(name, extra = {}) {
@@ -438,6 +473,75 @@ const field = (fs, n) => fs.find((f) => f.num === n)
438
473
  const str = (fs, n) => field(fs, n)?.data.toString("utf8") ?? ""
439
474
  const num = (fs, n) => field(fs, n)?.n ?? 0
440
475
 
476
+ // StepCalls is what Cursor says of a step's calls besides its execs
477
+ // (plugins#550). InteractionUpdate 2 (tool_call_started), 3
478
+ // (tool_call_completed) and 7 (partial_tool_call) each carry the call's
479
+ // call_id (field 1). A call Cursor completed by itself, never started on
480
+ // the client and never sent as an exec, is in tool_requests_listed's count
481
+ // all the same, and no exec of it will come: 0.2.34 waited for it while
482
+ // Cursor waited for the one call it had sent, 240 s, heartbeats only.
483
+ // parse is the path's field reader.
484
+ export class StepCalls {
485
+ constructor(parse) {
486
+ this.parse = parse
487
+ this.started = new Set()
488
+ this.execs = new Set() // the short strings of each exec's args: its tool_call_id among them
489
+ this.partial = new Set() // calls the model is writing in this step
490
+ this.byCursor = new Set()
491
+ }
492
+ // update takes an InteractionUpdate's own field (num) and its fields; it
493
+ // says whether a call was completed by Cursor: one this step wrote (7)
494
+ // and completed (3) with no start on the client (2) and no exec. A
495
+ // completion of an earlier step's call is never counted again.
496
+ update(n, uf) {
497
+ if (n !== 2 && n !== 3 && n !== 7) return false
498
+ const id = str(uf, 1)
499
+ if (!id) return false
500
+ if (n === 2) this.started.add(id)
501
+ if (n === 7) this.partial.add(id)
502
+ if (n !== 3) return false
503
+ if (!this.partial.has(id) || this.started.has(id) || this.execs.has(id) || this.byCursor.has(id)) return false
504
+ this.byCursor.add(id)
505
+ return true
506
+ }
507
+ // exec notes an exec's args, whatever its kind: each kind keeps its
508
+ // tool_call_id at a field of its own (ShellArgs 4, ReadArgs 2, McpArgs 3)
509
+ exec(data) {
510
+ try {
511
+ for (const e of this.parse(data)) {
512
+ if (e.wire !== 2 || e.num === 15 || e.num === 19) continue
513
+ for (const a of this.parse(e.data)) if (a.wire === 2 && a.data.length && a.data.length <= 512) this.execs.add(a.data.toString("utf8"))
514
+ }
515
+ } catch {}
516
+ }
517
+ get completed() {
518
+ return this.byCursor.size
519
+ }
520
+ // writing is how many calls the step wrote (7) that are neither started
521
+ // on the client, sent as an exec, nor completed by Cursor: a call of
522
+ // Cursor's own still running, or one still to be sent
523
+ get writing() {
524
+ let n = 0
525
+ for (const id of this.partial) if (!this.started.has(id) && !this.execs.has(id) && !this.byCursor.has(id)) n++
526
+ return n
527
+ }
528
+ // nextStep starts the next step's count
529
+ nextStep() {
530
+ this.partial.clear()
531
+ this.byCursor.clear()
532
+ }
533
+ }
534
+ // pendingCalls is how many calls a checkpoint (ConversationStateStructure)
535
+ // says Cursor waits on: its pending_tool_calls, field 4; null when it
536
+ // can't be read
537
+ export function pendingCalls(data, parse = nativeFields) {
538
+ try {
539
+ return parse(data).filter((f) => f.num === 4).length
540
+ } catch {
541
+ return null
542
+ }
543
+ }
544
+
441
545
  function argsOf(s) {
442
546
  try {
443
547
  const v = typeof s === "string" ? JSON.parse(s) : s
@@ -737,6 +841,8 @@ class NativeSession {
737
841
  this.ac = new AbortController()
738
842
  this.execs = new Map()
739
843
  this.nativeCalls = new Map()
844
+ // what Cursor said of the step's calls besides its execs (plugins#550)
845
+ this.stepCalls = new StepCalls(nativeFields)
740
846
  this.blobs = new Map()
741
847
  this.knownHashes = new Set()
742
848
  this.blobSize = 0
@@ -934,6 +1040,7 @@ class NativeSession {
934
1040
  // caller's results, or after a step whose calls were all answered here.
935
1041
  nextStep(estimate) {
936
1042
  this.steps++
1043
+ this.stepCalls.nextStep()
937
1044
  this.quiet.nextStep(this.steps)
938
1045
  this.stepSaid = this.said
939
1046
  if (estimate != null) this.stepEstimate = estimate
@@ -951,7 +1058,7 @@ class NativeSession {
951
1058
  this.batchTimer = later(() => {
952
1059
  const s = this.segment
953
1060
  if (s.expected == null && s.calls.length && !this.resultSubmitted) this.missingCount()
954
- else this.fail({ status: 502, message: `Cursor did not complete its declared native tool batch: ${s.calls.length} of ${s.expected ?? "?"} calls came, ${s.answered} answered here, none since ${Date.now() - since} ms` })
1061
+ else this.fail({ status: 502, message: `Cursor did not complete its declared native tool batch: ${s.calls.length} of ${s.expected ?? "?"} calls came, ${s.answered} answered here${this.stepCalls.completed ? `, ${this.stepCalls.completed} completed by Cursor` : ""}, none since ${Date.now() - since} ms` })
955
1062
  }, this.limits.batchWait)
956
1063
  }
957
1064
  missingCount() {
@@ -967,6 +1074,23 @@ class NativeSession {
967
1074
  if (this.batchTimer) this.waitForBatch()
968
1075
  this.sealBatch()
969
1076
  }
1077
+ // A call of the step Cursor completed by itself (plugins#550): in its
1078
+ // count, and never sent as an exec.
1079
+ completedByCursor() {
1080
+ if (this.terminal || this.state === "awaiting-results") return
1081
+ if (this.batchTimer) this.waitForBatch()
1082
+ this.sealBatch()
1083
+ }
1084
+ // A checkpoint whose state waits on just the calls handed to the caller,
1085
+ // after the step's count, is a step Cursor has finished: what more the
1086
+ // count says will not come (plugins#550).
1087
+ waitedOn(data) {
1088
+ const s = this.segment
1089
+ if (this.terminal || this.state === "awaiting-results" || s.closed || s.expected == null || !s.calls.length) return
1090
+ if (pendingCalls(data) !== s.calls.length) return
1091
+ s.waitedOn = true
1092
+ this.sealBatch()
1093
+ }
970
1094
  listed(count) {
971
1095
  if (!Number.isSafeInteger(count) || count < 1) invalid("invalid native tool count", 502)
972
1096
  if (this.segment.expected != null && this.segment.expected !== count) {
@@ -986,7 +1110,8 @@ class NativeSession {
986
1110
  if (segment.expected == null) return
987
1111
  const made = segment.calls.length + segment.answered
988
1112
  if (made > segment.expected) invalid("native tool batch exceeded its declared count", 502)
989
- if (made !== segment.expected || segment.closed) return
1113
+ // the calls Cursor completed by itself make up the rest (plugins#550)
1114
+ if ((made + this.stepCalls.completed < segment.expected && !segment.waitedOn) || segment.closed) return
990
1115
  if (!segment.calls.length) {
991
1116
  // Every call of the step was answered here: Cursor goes on with the
992
1117
  // turn by itself, and its next step lists a count of its own.
@@ -1183,10 +1308,14 @@ class NativeSession {
1183
1308
  if (this.error) return
1184
1309
  } else if (u.num === 13) {
1185
1310
  if (this.batchTimer && !this.terminal && this.state !== "awaiting-results") this.waitForBatch()
1311
+ } else if ((u.num === 2 || u.num === 3 || u.num === 7) && this.stepCalls.update(u.num, uf)) {
1312
+ this.completedByCursor()
1313
+ if (this.error) return
1186
1314
  }
1187
1315
  }
1188
1316
  break
1189
1317
  case 2: {
1318
+ this.stepCalls.exec(m.data)
1190
1319
  const c = exec(m.data, this.chatTools, (b) => this.send(b), (id) => this.execClose(id), null, true)
1191
1320
  if (c) this.acceptCall(c)
1192
1321
  // request_context is Cursor asking for the environment, not a call
@@ -1195,6 +1324,7 @@ class NativeSession {
1195
1324
  }
1196
1325
  case 3:
1197
1326
  this.checkpointReceived(m.data)
1327
+ this.waitedOn(m.data)
1198
1328
  break
1199
1329
  case 4: {
1200
1330
  const kv = nativeFields(m.data)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-cursor-auth",
3
- "version": "0.2.34",
3
+ "version": "0.2.36",
4
4
  "description": "Cursor subscriptions (Pro, Pro+, Ultra, Teams) in OpenCode and magpie, through the API cursor-agent talks to",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",