@prevalentware/opencode-goal-plugin 0.1.45 → 0.1.46
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 +13 -1
- package/dist/server.js +56 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -160,6 +160,7 @@ Defaults:
|
|
|
160
160
|
- `defer_while_tasks_active`: `true`; when enabled, goal auto-continuation waits for active OpenCode Task child sessions and their orchestrator reconciliation before sending the next goal prompt.
|
|
161
161
|
- `max_auto_turns`: `25`
|
|
162
162
|
- `min_continue_interval_seconds`: `3`
|
|
163
|
+
- Fast V2 executions that finish inside this interval schedule a delayed continuation; they do not require another user message to wake up.
|
|
163
164
|
- `max_turn_time`: unset by default; set a positive number of seconds to retry one active-goal continuation prompt when a model turn remains busy for that long. Each new busy event resets the watchdog. Idle, built-in retry, session deletion, active Task children, and restricted agents suppress the retry. Watchdog retries are independent of `min_continue_interval_seconds` and never consume auto-turn or no-progress budgets, but recognized transport failures still count toward the `max_prompt_failures` ceiling.
|
|
164
165
|
- `max_prompt_failures`: `3`; consecutive transport or no-response continuation failures pause the goal at this ceiling. Prompt delivery alone does not reset the count; substantive assistant or tool progress, a new goal, or an explicit resume does.
|
|
165
166
|
- `default_token_budget`: unset by default; when set, new goals inherit this token budget.
|
|
@@ -253,6 +254,17 @@ bun run build
|
|
|
253
254
|
npm pack --dry-run
|
|
254
255
|
```
|
|
255
256
|
|
|
257
|
+
With `opencode2` installed, run `bun run build && bun run smoke:v2` to exercise the
|
|
258
|
+
native V2 lifecycle, not just mocked events. The smoke test uses a private server,
|
|
259
|
+
isolated home/config/database/goal state, and a deterministic local model with no
|
|
260
|
+
real provider credentials. It invokes `/goal`, requires **two** automatic
|
|
261
|
+
continuations with the default minimum interval, then verifies completion. A
|
|
262
|
+
second loaded location checks that server-wide events do not duplicate delivery.
|
|
263
|
+
The test prints its temporary artifact directory and shuts down its private
|
|
264
|
+
server. Use `OPENCODE_V2_BIN` to select another V2 binary, or run
|
|
265
|
+
`bun run smoke:v2 @prevalentware/opencode-goal-plugin@<version>` to install and
|
|
266
|
+
verify an exact published package in the isolated environment.
|
|
267
|
+
|
|
256
268
|
## Publishing
|
|
257
269
|
|
|
258
270
|
This package is set up for npm Trusted Publishing from GitHub Actions. On every push to `main`, CI runs typecheck, lint, and unit tests in parallel. If they all pass, the publish job computes the next patch version from the latest version on npm, builds the package, and runs `npm publish`.
|
|
@@ -279,6 +291,6 @@ OpenCode plugin modules are target-specific. This package exports separate modul
|
|
|
279
291
|
}
|
|
280
292
|
```
|
|
281
293
|
|
|
282
|
-
Codex goal mode has deeper runtime integration for thread lifecycle control. This plugin implements the same workflow using OpenCode plugin hooks. Token usage is read from OpenCode step-finish usage when available and falls back to message token metadata or text estimation when exact usage is unavailable. Continuation is driven by
|
|
294
|
+
Codex goal mode has deeper runtime integration for thread lifecycle control. This plugin implements the same workflow using OpenCode plugin hooks. Token usage is read from OpenCode step-finish usage when available and falls back to message token metadata or text estimation when exact usage is unavailable. Continuation is driven by V2 `session.execution.succeeded` events and legacy `session.idle` / `session.status` idle notifications, never by intermediate model-step completion. V2 execution starts arm busy tracking; native `session.retry.scheduled` events cancel plugin recovery while OpenCode retries. Terminal execution transport failures use bounded recovery, while interruptions (user, shutdown, or superseded) cancel local timers without starting another turn or charging a prompt failure. Interrupted or non-transport-failed executions remain suppressed until the host starts a new execution; this does not change the persisted goal status. Use `/pause_goal` for a durable pause. Each V2 plugin instance handles goal events only for its own location while still observing cross-location child task lifecycles. The optional `max_turn_time` watchdog can retry one goal continuation prompt when a model turn remains busy, without consuming the goal's auto-turn or no-progress budgets; recognized transport failures do count toward the prompt-failure ceiling. By default, continuation is deferred while OpenCode Task child sessions are active or their terminal result still needs an orchestrator turn. During compaction, the plugin disables OpenCode's generic synthetic auto-continue while an active goal exists so the goal-specific continuation prompt remains authoritative.
|
|
283
295
|
|
|
284
296
|
The goal sidebar shows the current status, elapsed time, token usage, auto-continue count, latest checkpoint, latest status message, stop reason, and objective when a goal is active, paused, or safety-limited. Closed goals remain visible briefly through the latest tool state as achieved or unmet.
|
package/dist/server.js
CHANGED
|
@@ -2687,6 +2687,7 @@ async function setupV2(context) {
|
|
|
2687
2687
|
const planAgents = restrictedAgentSet(options);
|
|
2688
2688
|
const isPlanAgent = (agent) => typeof agent === "string" && planAgents.has(agent.trim().toLowerCase());
|
|
2689
2689
|
const activeContinuationsV2 = new Set;
|
|
2690
|
+
const stoppedExecutions = new Set;
|
|
2690
2691
|
const latestStepBySession = new Map;
|
|
2691
2692
|
const stepTextBuffers = new Map;
|
|
2692
2693
|
const stepTokenSums = new Map;
|
|
@@ -2833,6 +2834,8 @@ async function setupV2(context) {
|
|
|
2833
2834
|
async function runAutoContinue(sessionID, fromTaskDeferral = false, scheduled) {
|
|
2834
2835
|
if (disposed)
|
|
2835
2836
|
return;
|
|
2837
|
+
if (stoppedExecutions.has(sessionID))
|
|
2838
|
+
return;
|
|
2836
2839
|
if (busySessions.has(sessionID))
|
|
2837
2840
|
return;
|
|
2838
2841
|
if (activeContinuationsV2.has(sessionID))
|
|
@@ -2913,8 +2916,13 @@ async function setupV2(context) {
|
|
|
2913
2916
|
if (nativeRetrySessions.has(sessionID))
|
|
2914
2917
|
return;
|
|
2915
2918
|
const goal = await reserveContinuation(sessionID, maxAutoTurns, minInterval);
|
|
2916
|
-
if (!goal)
|
|
2919
|
+
if (!goal) {
|
|
2920
|
+
const waiting = await getGoalInternal(sessionID);
|
|
2921
|
+
if (waiting?.status === "active" && waiting.pendingAttempt == null && waiting.lastContinuationAt != null && minInterval > 0) {
|
|
2922
|
+
scheduleSettledContinuation(sessionID, continuationDelayFromSnapshot(minInterval, waiting.lastContinuationAt), scheduled != null);
|
|
2923
|
+
}
|
|
2917
2924
|
return;
|
|
2925
|
+
}
|
|
2918
2926
|
attemptReservedAt = goal.pendingAttempt?.reservedAt ?? Date.now();
|
|
2919
2927
|
if (nativeRetrySessions.has(sessionID)) {
|
|
2920
2928
|
await rollbackContinuationAttempt(sessionID);
|
|
@@ -2955,6 +2963,23 @@ async function setupV2(context) {
|
|
|
2955
2963
|
async function handleV2Event(event) {
|
|
2956
2964
|
const data = event.data;
|
|
2957
2965
|
const sessionID = typeof data.sessionID === "string" ? data.sessionID : undefined;
|
|
2966
|
+
if (context.location && event.location && (event.location.directory !== context.location.directory || event.location.workspaceID !== context.location.workspaceID)) {
|
|
2967
|
+
if (event.type === "session.created" && sessionID && typeof data.parentID === "string") {
|
|
2968
|
+
taskTracker.observeSessionCreated({ properties: { info: { id: sessionID, parentID: data.parentID } } });
|
|
2969
|
+
} else if (sessionID) {
|
|
2970
|
+
if (event.type === "session.execution.started")
|
|
2971
|
+
taskTracker.observeSessionStatus(sessionID, "busy");
|
|
2972
|
+
if (["session.execution.succeeded", "session.execution.failed", "session.execution.interrupted", "session.idle"].includes(event.type)) {
|
|
2973
|
+
taskTracker.observeSessionStatus(sessionID, "idle");
|
|
2974
|
+
}
|
|
2975
|
+
if (event.type === "session.status" && isRecord(data.status) && typeof data.status.type === "string") {
|
|
2976
|
+
taskTracker.observeSessionStatus(sessionID, data.status.type);
|
|
2977
|
+
}
|
|
2978
|
+
if (event.type === "session.deleted")
|
|
2979
|
+
taskTracker.observeSessionDeleted(sessionID);
|
|
2980
|
+
}
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2958
2983
|
switch (event.type) {
|
|
2959
2984
|
case "session.created": {
|
|
2960
2985
|
const parentID = data.parentID;
|
|
@@ -2963,10 +2988,13 @@ async function setupV2(context) {
|
|
|
2963
2988
|
}
|
|
2964
2989
|
return;
|
|
2965
2990
|
}
|
|
2991
|
+
case "session.execution.started":
|
|
2992
|
+
case "session.retry.scheduled":
|
|
2966
2993
|
case "session.status": {
|
|
2967
|
-
const status = data.status;
|
|
2994
|
+
const status = event.type === "session.execution.started" ? { type: "busy" } : event.type === "session.retry.scheduled" ? { type: "retry" } : data.status;
|
|
2968
2995
|
if (sessionID && isRecord(status) && typeof status.type === "string") {
|
|
2969
2996
|
if (status.type === "busy") {
|
|
2997
|
+
stoppedExecutions.delete(sessionID);
|
|
2970
2998
|
busySessions.add(sessionID);
|
|
2971
2999
|
nativeRetrySessions.delete(sessionID);
|
|
2972
3000
|
armTurnWatchdog(sessionID);
|
|
@@ -2992,6 +3020,7 @@ async function setupV2(context) {
|
|
|
2992
3020
|
}
|
|
2993
3021
|
return;
|
|
2994
3022
|
}
|
|
3023
|
+
case "session.execution.succeeded":
|
|
2995
3024
|
case "session.idle": {
|
|
2996
3025
|
if (sessionID) {
|
|
2997
3026
|
busySessions.delete(sessionID);
|
|
@@ -3007,9 +3036,25 @@ async function setupV2(context) {
|
|
|
3007
3036
|
}
|
|
3008
3037
|
return;
|
|
3009
3038
|
}
|
|
3039
|
+
case "session.execution.interrupted": {
|
|
3040
|
+
if (!sessionID)
|
|
3041
|
+
return;
|
|
3042
|
+
stoppedExecutions.add(sessionID);
|
|
3043
|
+
busySessions.delete(sessionID);
|
|
3044
|
+
nativeRetrySessions.delete(sessionID);
|
|
3045
|
+
clearTurnWatchdog(sessionID);
|
|
3046
|
+
watchdogRescuedSessions.delete(sessionID);
|
|
3047
|
+
cancelScheduledContinuation(sessionID);
|
|
3048
|
+
taskDeferredSessions.delete(sessionID);
|
|
3049
|
+
taskTracker.observeSessionStatus(sessionID, "idle");
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
case "session.execution.failed":
|
|
3010
3053
|
case "session.error": {
|
|
3011
3054
|
if (!sessionID)
|
|
3012
3055
|
return;
|
|
3056
|
+
if (event.type === "session.execution.failed")
|
|
3057
|
+
nativeRetrySessions.delete(sessionID);
|
|
3013
3058
|
const inNativeRetry = nativeRetrySessions.has(sessionID);
|
|
3014
3059
|
busySessions.delete(sessionID);
|
|
3015
3060
|
clearTurnWatchdog(sessionID);
|
|
@@ -3018,6 +3063,13 @@ async function setupV2(context) {
|
|
|
3018
3063
|
nativeRetrySessions.delete(sessionID);
|
|
3019
3064
|
watchdogRescuedSessions.delete(sessionID);
|
|
3020
3065
|
const errorMessage = transportErrorMessageFromEvent(data);
|
|
3066
|
+
if (event.type === "session.execution.failed" && !isTransportError(errorMessage)) {
|
|
3067
|
+
stoppedExecutions.add(sessionID);
|
|
3068
|
+
cancelScheduledContinuation(sessionID);
|
|
3069
|
+
taskDeferredSessions.delete(sessionID);
|
|
3070
|
+
}
|
|
3071
|
+
if (event.type === "session.execution.failed")
|
|
3072
|
+
taskTracker.observeSessionStatus(sessionID, "idle");
|
|
3021
3073
|
if (errorMessage && isTransportError(errorMessage)) {
|
|
3022
3074
|
const goal = await getGoalInternal(sessionID);
|
|
3023
3075
|
if (goal?.status === "active") {
|
|
@@ -3041,6 +3093,7 @@ async function setupV2(context) {
|
|
|
3041
3093
|
case "session.deleted": {
|
|
3042
3094
|
if (!sessionID)
|
|
3043
3095
|
return;
|
|
3096
|
+
stoppedExecutions.delete(sessionID);
|
|
3044
3097
|
busySessions.delete(sessionID);
|
|
3045
3098
|
clearTurnWatchdog(sessionID);
|
|
3046
3099
|
watchdogRescuedSessions.delete(sessionID);
|
|
@@ -3317,6 +3370,7 @@ async function setupV2(context) {
|
|
|
3317
3370
|
clearTimeout(watchdog.timer);
|
|
3318
3371
|
turnWatchdogs.clear();
|
|
3319
3372
|
activeContinuationsV2.clear();
|
|
3373
|
+
stoppedExecutions.clear();
|
|
3320
3374
|
nativeRetrySessions.clear();
|
|
3321
3375
|
locallyDeliveredPendingSessions.clear();
|
|
3322
3376
|
watchdogRescuedSessions.clear();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prevalentware/opencode-goal-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.46",
|
|
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",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"lint": "eslint .",
|
|
50
50
|
"pack:dry-run": "npm pack --dry-run",
|
|
51
51
|
"test": "bun test",
|
|
52
|
+
"smoke:v2": "bun scripts/smoke-v2-lifecycle.ts",
|
|
52
53
|
"test:coverage": "bun test --coverage",
|
|
53
54
|
"typecheck": "tsc --noEmit",
|
|
54
55
|
"prepublishOnly": "bun run test && bun run build"
|