@prevalentware/opencode-goal-plugin 0.1.61 → 0.1.63

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.
Files changed (2) hide show
  1. package/dist/server.js +116 -14
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -3263,6 +3263,7 @@ var server = async ({ client }, options) => {
3263
3263
  const scheduledContinuations = new Map;
3264
3264
  const turnWatchdogs = new Map;
3265
3265
  const busySessions = new Set;
3266
+ const busyRevisions = new Map;
3266
3267
  const nativeRetrySessions = new Set;
3267
3268
  const locallyDeliveredPendingSessions = new Set;
3268
3269
  const toolAttempts = new Map;
@@ -3458,13 +3459,52 @@ var server = async ({ client }, options) => {
3458
3459
  maybeUnref.unref();
3459
3460
  scheduledContinuations.set(sessionID, scheduled);
3460
3461
  }
3462
+ async function rearmBusyContinuation(sessionID, scheduled) {
3463
+ if (scheduled && scheduledContinuations.get(sessionID) !== scheduled)
3464
+ return;
3465
+ const goal = await getGoalInternal(sessionID);
3466
+ if (!taskDeferralGoalContinuable(goal)) {
3467
+ cancelScheduledContinuation(sessionID);
3468
+ return;
3469
+ }
3470
+ scheduleSettledContinuation(sessionID, TASK_BLOCK_RETRY_MS, scheduled != null, scheduled?.purpose ?? "settle");
3471
+ }
3472
+ async function scheduledSessionBusy(sessionID, scheduled) {
3473
+ if (!scheduled)
3474
+ return busySessions.has(sessionID);
3475
+ const session = client.session;
3476
+ if (!session.status)
3477
+ return busySessions.has(sessionID);
3478
+ const busyRevision = busyRevisions.get(sessionID) ?? 0;
3479
+ try {
3480
+ const result = await session.status();
3481
+ if (!isRecord(result) || result.error != null)
3482
+ return true;
3483
+ const statuses = isRecord(result.data) ? result.data : ("data" in result) || ("response" in result) ? null : result;
3484
+ if (!statuses)
3485
+ return true;
3486
+ const status = statuses[sessionID];
3487
+ if (isRecord(status) && (status.type === "busy" || status.type === "retry"))
3488
+ return true;
3489
+ if ((status == null || isRecord(status) && status.type === "idle") && (busyRevisions.get(sessionID) ?? 0) === busyRevision) {
3490
+ busySessions.delete(sessionID);
3491
+ }
3492
+ } catch {
3493
+ return true;
3494
+ }
3495
+ return busySessions.has(sessionID);
3496
+ }
3461
3497
  async function runAutoContinue(sessionID, fromTaskDeferral = false, scheduled) {
3462
3498
  if (disposed)
3463
3499
  return;
3464
- if (busySessions.has(sessionID))
3500
+ if (busySessions.has(sessionID) && !scheduled) {
3501
+ await rearmBusyContinuation(sessionID);
3465
3502
  return;
3466
- if (activeContinuations.has(sessionID))
3503
+ }
3504
+ if (activeContinuations.has(sessionID)) {
3505
+ await rearmBusyContinuation(sessionID, scheduled);
3467
3506
  return;
3507
+ }
3468
3508
  const epoch = continuationEpochs.current(sessionID);
3469
3509
  const isCurrent = () => !disposed && epoch === continuationEpochs.current(sessionID);
3470
3510
  activeContinuations.add(sessionID);
@@ -3472,6 +3512,10 @@ var server = async ({ client }, options) => {
3472
3512
  let attemptGoalID;
3473
3513
  let attemptID;
3474
3514
  try {
3515
+ if (await scheduledSessionBusy(sessionID, scheduled)) {
3516
+ await rearmBusyContinuation(sessionID, scheduled);
3517
+ return;
3518
+ }
3475
3519
  const latestAssistant = await fetchLatestAssistant(client, sessionID);
3476
3520
  if (!isCurrent())
3477
3521
  return;
@@ -3492,8 +3536,12 @@ var server = async ({ client }, options) => {
3492
3536
  scheduleSettledContinuation(sessionID, taskStatus.retryAt != null ? taskStatus.retryAt - Date.now() : TASK_BLOCK_RETRY_MS, scheduled != null || taskStatus.retryAt != null);
3493
3537
  return;
3494
3538
  }
3495
- if (!isCurrent() || busySessions.has(sessionID))
3539
+ if (!isCurrent())
3540
+ return;
3541
+ if (busySessions.has(sessionID)) {
3542
+ await rearmBusyContinuation(sessionID, scheduled);
3496
3543
  return;
3544
+ }
3497
3545
  const observed = await recordAssistantMessage(sessionID, latestAssistant, options ?? {}, true);
3498
3546
  await reconcileLocalMarkerAfterProgress(locallyDeliveredPendingSessions, sessionID, observed.goal);
3499
3547
  const queued = scheduledContinuations.get(sessionID);
@@ -3510,8 +3558,10 @@ var server = async ({ client }, options) => {
3510
3558
  await pauseGoalForPlanMode(sessionID);
3511
3559
  return;
3512
3560
  }
3513
- if (busySessions.has(sessionID))
3561
+ if (busySessions.has(sessionID)) {
3562
+ await rearmBusyContinuation(sessionID, scheduled);
3514
3563
  return;
3564
+ }
3515
3565
  if (!fromTaskDeferral && taskDeferredSessions.has(sessionID)) {
3516
3566
  scheduleSettledContinuation(sessionID);
3517
3567
  return;
@@ -3549,8 +3599,11 @@ var server = async ({ client }, options) => {
3549
3599
  attemptGoalID = goal.id;
3550
3600
  attemptID = goal.pendingAttempt?.id;
3551
3601
  const beforeDelivery = await getGoalInternal(sessionID);
3552
- if (!isCurrent() || !continuationStillReserved(goal, beforeDelivery) || busySessions.has(sessionID) || nativeRetrySessions.has(sessionID)) {
3602
+ const busyBeforeDelivery = await scheduledSessionBusy(sessionID, scheduled);
3603
+ if (!isCurrent() || !continuationStillReserved(goal, beforeDelivery) || busyBeforeDelivery || nativeRetrySessions.has(sessionID)) {
3553
3604
  await rollbackContinuationAttempt(sessionID, { goalID: attemptGoalID, attemptID });
3605
+ if (isCurrent() && busyBeforeDelivery)
3606
+ await rearmBusyContinuation(sessionID, scheduled);
3554
3607
  return;
3555
3608
  }
3556
3609
  if (scheduled && scheduledContinuations.get(sessionID) !== scheduled) {
@@ -3871,6 +3924,7 @@ var server = async ({ client }, options) => {
3871
3924
  if (isRecord(status) && typeof status.type === "string") {
3872
3925
  if (status.type === "busy") {
3873
3926
  busySessions.add(sessionID);
3927
+ busyRevisions.set(sessionID, (busyRevisions.get(sessionID) ?? 0) + 1);
3874
3928
  nativeRetrySessions.delete(sessionID);
3875
3929
  }
3876
3930
  if (status.type === "busy")
@@ -3934,6 +3988,7 @@ var server = async ({ client }, options) => {
3934
3988
  continuationEpochs.invalidate(sessionID);
3935
3989
  explicitResumeRequests.delete(sessionID);
3936
3990
  busySessions.delete(sessionID);
3991
+ busyRevisions.delete(sessionID);
3937
3992
  clearTurnWatchdog(sessionID);
3938
3993
  watchdogRescuedSessions.delete(sessionID);
3939
3994
  locallyDeliveredPendingSessions.delete(sessionID);
@@ -3987,6 +4042,7 @@ async function setupV2(context) {
3987
4042
  const scheduledContinuations = new Map;
3988
4043
  const turnWatchdogs = new Map;
3989
4044
  const busySessions = new Set;
4045
+ const busyRevisions = new Map;
3990
4046
  const nativeRetrySessions = new Set;
3991
4047
  const locallyDeliveredPendingSessions = new Set;
3992
4048
  const watchdogRescuedSessions = new Set;
@@ -4189,25 +4245,59 @@ async function setupV2(context) {
4189
4245
  maybeUnref.unref();
4190
4246
  scheduledContinuations.set(sessionID, scheduled);
4191
4247
  }
4248
+ async function rearmBusyContinuation(sessionID, scheduled) {
4249
+ if (scheduled && scheduledContinuations.get(sessionID) !== scheduled)
4250
+ return;
4251
+ const goal = await getGoalInternal(sessionID);
4252
+ if (!taskDeferralGoalContinuable(goal)) {
4253
+ cancelScheduledContinuation(sessionID);
4254
+ return;
4255
+ }
4256
+ scheduleSettledContinuation(sessionID, TASK_BLOCK_RETRY_MS, scheduled != null, scheduled?.purpose ?? "settle");
4257
+ }
4192
4258
  async function runAutoContinue(sessionID, fromTaskDeferral = false, scheduled) {
4193
4259
  if (disposed)
4194
4260
  return;
4195
4261
  if (stoppedExecutions.has(sessionID))
4196
4262
  return;
4197
- if (busySessions.has(sessionID))
4263
+ if (busySessions.has(sessionID) && !scheduled) {
4264
+ await rearmBusyContinuation(sessionID);
4198
4265
  return;
4199
- if (activeContinuationsV2.has(sessionID))
4266
+ }
4267
+ if (activeContinuationsV2.has(sessionID)) {
4268
+ await rearmBusyContinuation(sessionID, scheduled);
4200
4269
  return;
4270
+ }
4201
4271
  const epoch = continuationEpochs.current(sessionID);
4202
4272
  const isCurrent = () => !disposed && epoch === continuationEpochs.current(sessionID);
4203
4273
  await taskRecoveryComplete;
4204
- if (!isCurrent() || stoppedExecutions.has(sessionID) || busySessions.has(sessionID))
4274
+ if (!isCurrent() || stoppedExecutions.has(sessionID))
4205
4275
  return;
4206
4276
  activeContinuationsV2.add(sessionID);
4207
4277
  let attemptReservedAt = Date.now();
4208
4278
  let attemptGoalID;
4209
4279
  let attemptID;
4210
4280
  try {
4281
+ if (scheduled && typeof context.session.wait === "function") {
4282
+ const busyRevision = busyRevisions.get(sessionID) ?? 0;
4283
+ try {
4284
+ const signal = AbortSignal.any([abortController.signal, AbortSignal.timeout(TASK_BLOCK_RETRY_MS)]);
4285
+ await context.session.wait({ sessionID }, { signal });
4286
+ } catch {
4287
+ if (!isCurrent() || abortController.signal.aborted)
4288
+ return;
4289
+ await rearmBusyContinuation(sessionID, scheduled);
4290
+ return;
4291
+ }
4292
+ if ((busyRevisions.get(sessionID) ?? 0) === busyRevision)
4293
+ busySessions.delete(sessionID);
4294
+ }
4295
+ if (!isCurrent() || stoppedExecutions.has(sessionID))
4296
+ return;
4297
+ if (busySessions.has(sessionID)) {
4298
+ await rearmBusyContinuation(sessionID, scheduled);
4299
+ return;
4300
+ }
4211
4301
  const latestStep = latestStepBySession.get(sessionID);
4212
4302
  if (latestStep?.messageID) {
4213
4303
  taskTracker.observeAssistantMessage(sessionID, { info: { id: latestStep.messageID, role: "assistant" } });
@@ -4226,8 +4316,10 @@ async function setupV2(context) {
4226
4316
  scheduleSettledContinuation(sessionID, taskStatus.retryAt != null ? taskStatus.retryAt - Date.now() : TASK_BLOCK_RETRY_MS, scheduled != null);
4227
4317
  return;
4228
4318
  }
4229
- if (busySessions.has(sessionID))
4319
+ if (busySessions.has(sessionID)) {
4320
+ await rearmBusyContinuation(sessionID, scheduled);
4230
4321
  return;
4322
+ }
4231
4323
  if (latestStep) {
4232
4324
  const beforeProgress = await getGoalInternal(sessionID);
4233
4325
  const after = await recordAssistantProgress(sessionID, {
@@ -4256,8 +4348,10 @@ async function setupV2(context) {
4256
4348
  await pauseGoalForPlanMode(sessionID);
4257
4349
  return;
4258
4350
  }
4259
- if (busySessions.has(sessionID))
4351
+ if (busySessions.has(sessionID)) {
4352
+ await rearmBusyContinuation(sessionID, scheduled);
4260
4353
  return;
4354
+ }
4261
4355
  if (!fromTaskDeferral && taskDeferredSessions.has(sessionID)) {
4262
4356
  scheduleSettledContinuation(sessionID);
4263
4357
  return;
@@ -4302,6 +4396,8 @@ async function setupV2(context) {
4302
4396
  const beforeDelivery = await getGoalInternal(sessionID);
4303
4397
  if (!isCurrent() || !continuationStillReserved(goal, beforeDelivery) || busySessions.has(sessionID) || nativeRetrySessions.has(sessionID)) {
4304
4398
  await rollbackContinuationAttempt(sessionID, { goalID: attemptGoalID, attemptID });
4399
+ if (isCurrent() && busySessions.has(sessionID))
4400
+ await rearmBusyContinuation(sessionID, scheduled);
4305
4401
  return;
4306
4402
  }
4307
4403
  if (scheduled && scheduledContinuations.get(sessionID) !== scheduled) {
@@ -4436,6 +4532,7 @@ async function setupV2(context) {
4436
4532
  if (status.type === "busy") {
4437
4533
  stoppedExecutions.delete(sessionID);
4438
4534
  busySessions.add(sessionID);
4535
+ busyRevisions.set(sessionID, (busyRevisions.get(sessionID) ?? 0) + 1);
4439
4536
  nativeRetrySessions.delete(sessionID);
4440
4537
  armTurnWatchdog(sessionID);
4441
4538
  await markPendingContinuationStarted(sessionID);
@@ -4537,6 +4634,7 @@ async function setupV2(context) {
4537
4634
  stoppedExecutions.delete(sessionID);
4538
4635
  sessionOwnership.delete(sessionID);
4539
4636
  busySessions.delete(sessionID);
4637
+ busyRevisions.delete(sessionID);
4540
4638
  clearTurnWatchdog(sessionID);
4541
4639
  watchdogRescuedSessions.delete(sessionID);
4542
4640
  locallyDeliveredPendingSessions.delete(sessionID);
@@ -4865,15 +4963,19 @@ async function setupV2(context) {
4865
4963
  cancelScheduledContinuation(sessionID);
4866
4964
  }
4867
4965
  }));
4868
- registrations.push(await context.session.hook("context", (sessionContext) => {
4869
- const reminder = systemReminder(locale);
4870
- if (sessionContext.system.some((part) => part.type === "text" && part.text.includes(reminder)))
4966
+ const reminder = systemReminder(locale);
4967
+ const ensureSystemReminder = (system) => {
4968
+ if (system.some((part) => part.type === "text" && part.text.includes(reminder)))
4871
4969
  return;
4872
- sessionContext.system.push({ type: "text", text: reminder });
4970
+ system.push({ type: "text", text: reminder });
4971
+ };
4972
+ registrations.push(await context.session.hook("context", (sessionContext) => {
4973
+ ensureSystemReminder(sessionContext.system);
4873
4974
  }));
4874
4975
  try {
4875
4976
  const hookCompaction = context.session.hook;
4876
4977
  registrations.push(await hookCompaction("compaction", async (event) => {
4978
+ ensureSystemReminder(event.system);
4877
4979
  const goal = await getGoal(event.sessionID);
4878
4980
  if (!goal)
4879
4981
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prevalentware/opencode-goal-plugin",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "description": "OpenCode goal plugin that adds Codex-style long-running goal mode, /goal commands, persistence, and TUI status for AI coding agents.",
5
5
  "keywords": [
6
6
  "opencode",