@bojackduy/opencode-loopd 1.9.6 → 1.9.7

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 +36 -13
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -1334,7 +1334,7 @@ function createLoopEngine(options) {
1334
1334
  revision: blockedState.revision
1335
1335
  });
1336
1336
  if (shouldNotifyBlocked) {
1337
- await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" stopped: ${limitResult.reason} (child did not wrap up). Status: blocked. Last progress: ${goal.lastProgress?.summary || "none"}.`);
1337
+ await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" stopped: ${limitResult.reason} (child did not wrap up). Status: blocked. Last progress: ${goal.lastProgress?.summary || "none"}.`, goal.parentAgent);
1338
1338
  }
1339
1339
  return true;
1340
1340
  }
@@ -1473,7 +1473,7 @@ function createLoopEngine(options) {
1473
1473
  revision: newState.revision
1474
1474
  });
1475
1475
  if (shouldNotify) {
1476
- await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" blocked after ${runtime.consecutiveFailures + 1} failures. Last error: ${message}.`);
1476
+ await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" blocked after ${runtime.consecutiveFailures + 1} failures. Last error: ${message}.`, goal.parentAgent);
1477
1477
  }
1478
1478
  }
1479
1479
  return true;
@@ -1635,7 +1635,7 @@ function createLoopEngine(options) {
1635
1635
  costUsed: stoppedGoal.costUsed
1636
1636
  });
1637
1637
  if (shouldNotify) {
1638
- await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" stopped: ${reason}. Worker aborted, status: budget_limited. Resume with resume_goal to continue spending.`);
1638
+ await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" stopped: ${reason}. Worker aborted, status: budget_limited. Resume with resume_goal to continue spending.`, goal.parentAgent);
1639
1639
  }
1640
1640
  return true;
1641
1641
  }
@@ -1745,7 +1745,7 @@ function createLoopEngine(options) {
1745
1745
  workerSessionID: goal.workerSessionID,
1746
1746
  count: unknownRuntime?.unknownStatusCount
1747
1747
  });
1748
- await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" worker is unreachable after ${unknownRuntime?.unknownStatusCount ?? unknownStatusThreshold} status checks. The goal remains active; use inspect_background_goal, nudge_goal, pause_goal, or resume_goal to recover it.`);
1748
+ await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" worker is unreachable after ${unknownRuntime?.unknownStatusCount ?? unknownStatusThreshold} status checks. The goal remains active; use inspect_background_goal, nudge_goal, pause_goal, or resume_goal to recover it.`, goal.parentAgent);
1749
1749
  }
1750
1750
  continue;
1751
1751
  }
@@ -1840,7 +1840,7 @@ function createLoopEngine(options) {
1840
1840
  stuckSeconds
1841
1841
  });
1842
1842
  const quietMinutes = Math.max(1, Math.floor(stuckSeconds / 60));
1843
- await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" worker is idle but its turn will not confirm (unconfirmed for ${quietMinutes}m, no activity). The goal remains active; use inspect_background_goal to look, nudge_goal to re-prompt, or pause_goal to stop it.`);
1843
+ await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" worker is idle but its turn will not confirm (unconfirmed for ${quietMinutes}m, no activity). The goal remains active; use inspect_background_goal to look, nudge_goal to re-prompt, or pause_goal to stop it.`, goal.parentAgent);
1844
1844
  continue;
1845
1845
  }
1846
1846
  if (status === "idle") {
@@ -1879,7 +1879,7 @@ function createLoopEngine(options) {
1879
1879
  runID: runtime.activeRunID,
1880
1880
  stuckSeconds
1881
1881
  });
1882
- await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" worker may be stuck: no activity for ${Math.floor(stuckSeconds / 60)}m while reporting ${status}, lease expired. The goal remains active; use inspect_background_goal to look, nudge_goal to re-prompt, or pause_goal to stop it.`);
1882
+ await host.notifyOwner(goal.ownerSessionID, `Loop goal "${goal.name}" worker may be stuck: no activity for ${Math.floor(stuckSeconds / 60)}m while reporting ${status}, lease expired. The goal remains active; use inspect_background_goal to look, nudge_goal to re-prompt, or pause_goal to stop it.`, goal.parentAgent);
1883
1883
  }
1884
1884
  }
1885
1885
  }
@@ -2070,15 +2070,27 @@ function createRealHost(client, directory) {
2070
2070
  await client.session.compact({ sessionID });
2071
2071
  } catch {}
2072
2072
  },
2073
- async notifyOwner(ownerSessionID, message) {
2073
+ async notifyOwner(ownerSessionID, message, agent) {
2074
2074
  if (shouldDedupParentNotify(ownerSessionID, message)) {
2075
2075
  await logServerEvent(directory, "parent.notify.deduped", { ownerSessionID, preview: message.slice(0, 160) });
2076
2076
  return;
2077
2077
  }
2078
+ let resolvedAgent = agent?.trim() || undefined;
2079
+ if (!resolvedAgent) {
2080
+ try {
2081
+ const sess = await client.session.get({ path: { id: ownerSessionID } });
2082
+ const liveAgent = sess?.data?.agent;
2083
+ if (typeof liveAgent === "string" && liveAgent.trim())
2084
+ resolvedAgent = liveAgent.trim();
2085
+ } catch {}
2086
+ }
2078
2087
  try {
2088
+ const body = { parts: [{ type: "text", text: message }] };
2089
+ if (resolvedAgent)
2090
+ body.agent = resolvedAgent;
2079
2091
  const result = await withTimeout(client.session.promptAsync({
2080
2092
  path: { id: ownerSessionID },
2081
- body: { parts: [{ type: "text", text: message }] }
2093
+ body
2082
2094
  }), 1e4, "OpenCode parent notify");
2083
2095
  if (result?.error) {
2084
2096
  await logServerEvent(directory, "parent.notify.failed", { ownerSessionID, detail: describeError(result.error) });
@@ -2361,6 +2373,17 @@ function createGoalService(host) {
2361
2373
  return withGoalOperation(id, () => startUnlocked(directory, input, id));
2362
2374
  }
2363
2375
  async function startUnlocked(directory, input, id) {
2376
+ let parentAgent = input.parentAgent;
2377
+ let parentModel = input.parentModel;
2378
+ if ((!parentAgent || !parentModel) && host.readSession) {
2379
+ try {
2380
+ const identity = await host.readSession(input.ownerSessionID);
2381
+ if (!parentAgent && identity?.agent)
2382
+ parentAgent = identity.agent;
2383
+ if (!parentModel && identity?.model)
2384
+ parentModel = `${identity.model.providerID}/${identity.model.modelID}`;
2385
+ } catch {}
2386
+ }
2364
2387
  const goal = createGoal({
2365
2388
  id,
2366
2389
  name: input.name,
@@ -2375,10 +2398,10 @@ function createGoalService(host) {
2375
2398
  });
2376
2399
  if (typeof input.costBudget === "number")
2377
2400
  goal.costBudget = input.costBudget;
2378
- if (input.parentAgent)
2379
- goal.parentAgent = input.parentAgent;
2380
- if (input.parentModel)
2381
- goal.parentModel = input.parentModel;
2401
+ if (parentAgent)
2402
+ goal.parentAgent = parentAgent;
2403
+ if (parentModel)
2404
+ goal.parentModel = parentModel;
2382
2405
  const artifactDir = goalArtifactDir(directory, id);
2383
2406
  goal.config.artifactDir = artifactDir;
2384
2407
  if (!goal.config.progressFile)
@@ -4432,7 +4455,7 @@ var server = async ({ client, directory }, pluginOptions) => {
4432
4455
  });
4433
4456
  }
4434
4457
  const message = parsed.status === "complete" ? `Loop goal "${goal.name}" completed: ${parsed.summary || ""}. Evidence: ${parsed.evidence || ""}. Artifacts: ${goal.config.artifactDir || "n/a"}.` : `Loop goal "${goal.name}" blocked: ${parsed.reason || ""}. Needed: ${parsed.needed || ""}.`;
4435
- await host.notifyOwner(goal.ownerSessionID, message);
4458
+ await host.notifyOwner(goal.ownerSessionID, message, goal.parentAgent);
4436
4459
  } catch {}
4437
4460
  }
4438
4461
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@bojackduy/opencode-loopd",
4
- "version": "1.9.6",
4
+ "version": "1.9.7",
5
5
  "description": "Codex-inspired background goal engine for OpenCode — autonomous subagents, engine-driven loop, child worker sessions and modal TUI dashboard. Like Claude Code loop for OpenCode.",
6
6
  "type": "module",
7
7
  "license": "AGPL-3.0-or-later",