@prevalentware/opencode-goal-plugin 0.1.20 → 0.1.21
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 +3 -3
- package/dist/server.js +48 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,8 +108,8 @@ Defaults:
|
|
|
108
108
|
- `max_prompt_failures`: `3`
|
|
109
109
|
- `default_token_budget`: unset by default; when set, new goals inherit this token budget.
|
|
110
110
|
- `max_goal_duration_seconds`: unset by default; when set, new goals inherit this elapsed-time safety limit.
|
|
111
|
-
- `no_progress_token_threshold`: `50
|
|
112
|
-
- `max_no_progress_turns`: `2
|
|
111
|
+
- `no_progress_token_threshold`: `50`; output-token floor used to judge whether a goal continuation turn made progress.
|
|
112
|
+
- `max_no_progress_turns`: `2`; consecutive low-progress goal continuation turns before pausing. Only turns produced by a reserved goal continuation count — ordinary low-output assistant messages (for example short tool-call-only turns from PTY or status checks) never increment this counter.
|
|
113
113
|
- `register_command`: `true`
|
|
114
114
|
- `command_name`: `"goal"`
|
|
115
115
|
- `restricted_agents`: `["plan"]`; agents (matched case-insensitively) treated as planning-only for goal execution.
|
|
@@ -138,7 +138,7 @@ The plugin also uses safety states while keeping the goal available for review o
|
|
|
138
138
|
|
|
139
139
|
- `budgetLimited` when a token budget is exhausted.
|
|
140
140
|
- `usageLimited` when an auto-turn or elapsed-time budget is exhausted.
|
|
141
|
-
- `paused` when the user pauses, auto-continue repeatedly fails, or repeated low-
|
|
141
|
+
- `paused` when the user pauses, auto-continue repeatedly fails, or repeated low-progress goal continuation turns are detected. No-progress accounting is scoped to goal continuation turns: each reserved continuation is evaluated once, when its turn completes, and unrelated assistant activity in the session never pauses the goal.
|
|
142
142
|
|
|
143
143
|
When a safety limit is reached, the plugin sends one wrap-up prompt asking for a concise handoff instead of silently continuing forever.
|
|
144
144
|
|
package/dist/server.js
CHANGED
|
@@ -63,7 +63,10 @@ var GoalSchema = Schema.Struct({
|
|
|
63
63
|
lastCheckpoint: Schema.optionalWith(Schema.NullOr(CheckpointSchema), { default: () => null }),
|
|
64
64
|
lastAssistantText: Schema.optionalWith(Schema.String, { default: () => "" }),
|
|
65
65
|
lastAssistantMessageID: Schema.optionalWith(Schema.String, { default: () => "" }),
|
|
66
|
-
lastPromptAgent: Schema.optionalWith(NullableString, { default: () => null })
|
|
66
|
+
lastPromptAgent: Schema.optionalWith(NullableString, { default: () => null }),
|
|
67
|
+
awaitingContinuationProgress: Schema.optionalWith(Schema.Boolean, { default: () => false }),
|
|
68
|
+
continuationBaselineMessageID: Schema.optionalWith(Schema.String, { default: () => "" }),
|
|
69
|
+
continuationBaselineSummary: Schema.optionalWith(Schema.String, { default: () => "" })
|
|
67
70
|
});
|
|
68
71
|
var StateSchema = Schema.Struct({
|
|
69
72
|
version: Schema.Literal(1),
|
|
@@ -168,6 +171,9 @@ function normalizeGoal(goal) {
|
|
|
168
171
|
goal.lastAssistantText ??= "";
|
|
169
172
|
goal.lastAssistantMessageID ??= "";
|
|
170
173
|
goal.lastPromptAgent ??= null;
|
|
174
|
+
goal.awaitingContinuationProgress = goal.awaitingContinuationProgress === true;
|
|
175
|
+
goal.continuationBaselineMessageID ??= "";
|
|
176
|
+
goal.continuationBaselineSummary ??= "";
|
|
171
177
|
goal.noProgressTurns = nonNegativeInteger(goal.noProgressTurns, 0);
|
|
172
178
|
goal.maxAutoTurns = positiveIntegerOrNull(goal.maxAutoTurns);
|
|
173
179
|
goal.maxDurationSeconds = positiveIntegerOrNull(goal.maxDurationSeconds);
|
|
@@ -247,6 +253,9 @@ function snapshot(goal) {
|
|
|
247
253
|
lastAssistantText: goal.lastAssistantText,
|
|
248
254
|
lastAssistantMessageID: goal.lastAssistantMessageID,
|
|
249
255
|
lastPromptAgent: goal.lastPromptAgent,
|
|
256
|
+
awaitingContinuationProgress: goal.awaitingContinuationProgress,
|
|
257
|
+
continuationBaselineMessageID: goal.continuationBaselineMessageID,
|
|
258
|
+
continuationBaselineSummary: goal.continuationBaselineSummary,
|
|
250
259
|
autoTurns: goal.autoTurns,
|
|
251
260
|
lastContinuationAt: goal.lastContinuationAt,
|
|
252
261
|
remainingTokens: remainingTokens(goal),
|
|
@@ -297,7 +306,10 @@ async function createGoal(sessionID, objective, options) {
|
|
|
297
306
|
lastCheckpoint: null,
|
|
298
307
|
lastAssistantText: "",
|
|
299
308
|
lastAssistantMessageID: "",
|
|
300
|
-
lastPromptAgent: normalizedOptions.agent
|
|
309
|
+
lastPromptAgent: normalizedOptions.agent,
|
|
310
|
+
awaitingContinuationProgress: false,
|
|
311
|
+
continuationBaselineMessageID: "",
|
|
312
|
+
continuationBaselineSummary: ""
|
|
301
313
|
};
|
|
302
314
|
pushHistory(goal, "created", goalLimitSummary(goal));
|
|
303
315
|
if (paused)
|
|
@@ -459,24 +471,28 @@ async function recordAssistantProgress(sessionID, input) {
|
|
|
459
471
|
goal.lastAssistantText = text;
|
|
460
472
|
if (messageID)
|
|
461
473
|
goal.lastAssistantMessageID = messageID;
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
goal.
|
|
469
|
-
goal.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
+
const continuationTurnCompleted = input.evaluateContinuation === true && goal.awaitingContinuationProgress && Boolean(messageID) && messageID !== goal.continuationBaselineMessageID;
|
|
475
|
+
if (continuationTurnCompleted) {
|
|
476
|
+
goal.awaitingContinuationProgress = false;
|
|
477
|
+
const lowOutput = outputTokens > 0 && outputTokens < (threshold ?? DEFAULT_NO_PROGRESS_TOKEN_THRESHOLD);
|
|
478
|
+
const changedSinceContinuation = Boolean(summary && summary !== goal.continuationBaselineSummary);
|
|
479
|
+
if (lowOutput && !changedSinceContinuation) {
|
|
480
|
+
goal.noProgressTurns += 1;
|
|
481
|
+
if (maxNoProgressTurns && goal.noProgressTurns >= maxNoProgressTurns) {
|
|
482
|
+
accountWallClock(goal);
|
|
483
|
+
goal.status = "paused";
|
|
484
|
+
goal.lastAccountedAt = null;
|
|
485
|
+
goal.stopReason = "no progress";
|
|
486
|
+
goal.blocker = `Auto-continue paused after ${goal.noProgressTurns} low-progress continuation turn(s). Resume the goal to retry.`;
|
|
487
|
+
goal.lastStatus = goal.blocker;
|
|
488
|
+
pushHistory(goal, "warning", goal.blocker);
|
|
489
|
+
} else {
|
|
490
|
+
goal.lastStatus = `Low-progress continuation turn detected (${goal.noProgressTurns}/${maxNoProgressTurns ?? "unbounded"}).`;
|
|
491
|
+
pushHistory(goal, "warning", goal.lastStatus);
|
|
492
|
+
}
|
|
474
493
|
} else {
|
|
475
|
-
goal.
|
|
476
|
-
pushHistory(goal, "warning", goal.lastStatus);
|
|
494
|
+
goal.noProgressTurns = 0;
|
|
477
495
|
}
|
|
478
|
-
} else if (changed || outputTokens >= (threshold ?? DEFAULT_NO_PROGRESS_TOKEN_THRESHOLD)) {
|
|
479
|
-
goal.noProgressTurns = 0;
|
|
480
496
|
}
|
|
481
497
|
goal.updatedAt = nowSeconds();
|
|
482
498
|
return snapshot(goal);
|
|
@@ -499,6 +515,8 @@ async function reserveContinuation(sessionID, maxAutoTurns, minIntervalSeconds)
|
|
|
499
515
|
return null;
|
|
500
516
|
goal.autoTurns += 1;
|
|
501
517
|
goal.lastContinuationAt = now;
|
|
518
|
+
goal.continuationBaselineMessageID = goal.lastAssistantMessageID;
|
|
519
|
+
goal.continuationBaselineSummary = summarizeText(goal.lastAssistantText);
|
|
502
520
|
goal.lastStatus = `Auto-continue ${goal.autoTurns} reserved.`;
|
|
503
521
|
pushHistory(goal, "autoContinue", goal.lastStatus);
|
|
504
522
|
goal.updatedAt = now;
|
|
@@ -514,11 +532,14 @@ async function recordContinuationResult(sessionID, result, maxFailures) {
|
|
|
514
532
|
goal.updatedAt = now;
|
|
515
533
|
if (result === "success") {
|
|
516
534
|
goal.continuationFailures = 0;
|
|
517
|
-
if (goal.status === "active")
|
|
535
|
+
if (goal.status === "active") {
|
|
518
536
|
goal.lastStatus = "Auto-continue prompt sent.";
|
|
537
|
+
goal.awaitingContinuationProgress = true;
|
|
538
|
+
}
|
|
519
539
|
return snapshot(goal);
|
|
520
540
|
}
|
|
521
541
|
goal.continuationFailures += 1;
|
|
542
|
+
goal.awaitingContinuationProgress = false;
|
|
522
543
|
goal.lastStatus = `Auto-continue failed ${goal.continuationFailures} time(s).`;
|
|
523
544
|
pushHistory(goal, "error", goal.lastStatus);
|
|
524
545
|
if (goal.continuationFailures >= maxFailures) {
|
|
@@ -883,13 +904,16 @@ function exactTokensFromMessage(message) {
|
|
|
883
904
|
return;
|
|
884
905
|
}
|
|
885
906
|
function outputTokensFromMessage(message) {
|
|
907
|
+
let total;
|
|
886
908
|
for (const part of message.parts ?? []) {
|
|
887
909
|
if (part && typeof part === "object" && part.type === "step-finish") {
|
|
888
910
|
const output = outputTokensFromRecord(part.tokens);
|
|
889
911
|
if (output != null)
|
|
890
|
-
|
|
912
|
+
total = (total ?? 0) + output;
|
|
891
913
|
}
|
|
892
914
|
}
|
|
915
|
+
if (total != null)
|
|
916
|
+
return total;
|
|
893
917
|
if (message.info && typeof message.info === "object")
|
|
894
918
|
return outputTokensFromRecord(message.info.tokens);
|
|
895
919
|
return;
|
|
@@ -1239,7 +1263,7 @@ class TaskTracker {
|
|
|
1239
1263
|
return false;
|
|
1240
1264
|
}
|
|
1241
1265
|
}
|
|
1242
|
-
async function recordAssistantMessage(sessionID, message, options) {
|
|
1266
|
+
async function recordAssistantMessage(sessionID, message, options, evaluateContinuation = false) {
|
|
1243
1267
|
if (!message)
|
|
1244
1268
|
return;
|
|
1245
1269
|
await recordAssistantProgress(sessionID, {
|
|
@@ -1247,7 +1271,8 @@ async function recordAssistantMessage(sessionID, message, options) {
|
|
|
1247
1271
|
text: textFromMessage(message),
|
|
1248
1272
|
outputTokens: outputTokensFromMessage(message) ?? null,
|
|
1249
1273
|
noProgressTokenThreshold: positiveIntegerOrNull2(options.no_progress_token_threshold),
|
|
1250
|
-
maxNoProgressTurns: positiveIntegerOrNull2(options.max_no_progress_turns)
|
|
1274
|
+
maxNoProgressTurns: positiveIntegerOrNull2(options.max_no_progress_turns),
|
|
1275
|
+
evaluateContinuation
|
|
1251
1276
|
});
|
|
1252
1277
|
}
|
|
1253
1278
|
function mergeSystemReminder(output, reminder) {
|
|
@@ -1329,7 +1354,7 @@ var server = async ({ client }, options) => {
|
|
|
1329
1354
|
}
|
|
1330
1355
|
if (busySessions.has(sessionID))
|
|
1331
1356
|
return;
|
|
1332
|
-
await recordAssistantMessage(sessionID, latestAssistant, options ?? {});
|
|
1357
|
+
await recordAssistantMessage(sessionID, latestAssistant, options ?? {}, true);
|
|
1333
1358
|
const current = await getGoal(sessionID);
|
|
1334
1359
|
if (!current)
|
|
1335
1360
|
return;
|
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.21",
|
|
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",
|