@prevalentware/opencode-goal-plugin 0.1.35 → 0.1.37
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/dist/server.js +162 -27
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -152,12 +152,20 @@ var PendingAttemptSchema = Schema.Struct({
|
|
|
152
152
|
armNoProgress: Schema.Boolean,
|
|
153
153
|
previousLastContinuationAt: Schema.NullOr(Schema.Number)
|
|
154
154
|
});
|
|
155
|
+
var UsageTrackerSchema = Schema.Struct({
|
|
156
|
+
baseline: Schema.optionalWith(Schema.Unknown, { default: () => null }),
|
|
157
|
+
lastObserved: Schema.optionalWith(Schema.Unknown, { default: () => null }),
|
|
158
|
+
baseTokens: Schema.optionalWith(Schema.Unknown, { default: () => null }),
|
|
159
|
+
pendingBaseline: Schema.optionalWith(Schema.Unknown, { default: () => null }),
|
|
160
|
+
pendingBaseTokens: Schema.optionalWith(Schema.Unknown, { default: () => null })
|
|
161
|
+
});
|
|
155
162
|
var GoalSchema = Schema.Struct({
|
|
156
163
|
sessionID: Schema.String,
|
|
157
164
|
objective: Schema.String,
|
|
158
165
|
status: Schema.Literal("active", "paused", "budgetLimited", "usageLimited", "complete", "unmet"),
|
|
159
166
|
tokenBudget: NullableNumber,
|
|
160
167
|
tokensUsed: Schema.Number,
|
|
168
|
+
usageTrackers: Schema.optionalWith(Schema.Record({ key: Schema.String, value: UsageTrackerSchema }), { default: () => ({}) }),
|
|
161
169
|
timeUsedSeconds: Schema.Number,
|
|
162
170
|
createdAt: Schema.Number,
|
|
163
171
|
updatedAt: Schema.Number,
|
|
@@ -315,12 +323,34 @@ function normalizeGoal(goal) {
|
|
|
315
323
|
goal.maxAutoTurns = positiveIntegerOrNull(goal.maxAutoTurns);
|
|
316
324
|
goal.maxDurationSeconds = positiveIntegerOrNull(goal.maxDurationSeconds);
|
|
317
325
|
goal.tokenBudget = positiveIntegerOrNull(goal.tokenBudget);
|
|
326
|
+
goal.usageTrackers = normalizeUsageTrackers(goal.usageTrackers);
|
|
318
327
|
goal.noProgressTokenThreshold = positiveIntegerOrNull(goal.noProgressTokenThreshold) ?? DEFAULT_NO_PROGRESS_TOKEN_THRESHOLD;
|
|
319
328
|
goal.maxNoProgressTurns = positiveIntegerOrNull(goal.maxNoProgressTurns) ?? DEFAULT_MAX_NO_PROGRESS_TURNS;
|
|
320
329
|
goal.budgetWrapupSent = goal.budgetWrapupSent === true;
|
|
321
330
|
goal.stopReason ??= null;
|
|
322
331
|
return goal;
|
|
323
332
|
}
|
|
333
|
+
function normalizeUsageTrackers(trackers) {
|
|
334
|
+
const normalized = {};
|
|
335
|
+
for (const [source, rawTracker] of Object.entries(trackers ?? {})) {
|
|
336
|
+
const tracker = rawTracker;
|
|
337
|
+
const baseline = nonNegativeIntegerOrNull(tracker?.baseline);
|
|
338
|
+
const lastObserved = nonNegativeIntegerOrNull(tracker?.lastObserved);
|
|
339
|
+
const baseTokens = nonNegativeIntegerOrNull(tracker?.baseTokens);
|
|
340
|
+
if (source && baseline != null && lastObserved != null && baseTokens != null && lastObserved >= baseline) {
|
|
341
|
+
const pendingBaseline = nonNegativeIntegerOrNull(tracker.pendingBaseline);
|
|
342
|
+
const pendingBaseTokens = nonNegativeIntegerOrNull(tracker.pendingBaseTokens);
|
|
343
|
+
normalized[source] = {
|
|
344
|
+
baseline,
|
|
345
|
+
lastObserved,
|
|
346
|
+
baseTokens,
|
|
347
|
+
pendingBaseline,
|
|
348
|
+
pendingBaseTokens: pendingBaseline == null ? null : pendingBaseTokens
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return normalized;
|
|
353
|
+
}
|
|
324
354
|
function normalizePendingAttempt(attempt) {
|
|
325
355
|
if (!attempt || typeof attempt !== "object")
|
|
326
356
|
return null;
|
|
@@ -365,6 +395,9 @@ function positiveIntegerOrNull(value) {
|
|
|
365
395
|
function nonNegativeInteger(value, fallback) {
|
|
366
396
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : fallback;
|
|
367
397
|
}
|
|
398
|
+
function nonNegativeIntegerOrNull(value) {
|
|
399
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
400
|
+
}
|
|
368
401
|
function isClosed(status) {
|
|
369
402
|
return status === "complete" || status === "unmet";
|
|
370
403
|
}
|
|
@@ -444,6 +477,7 @@ async function createGoal(sessionID, objective, options) {
|
|
|
444
477
|
status: normalizedOptions.initialStatus,
|
|
445
478
|
tokenBudget: normalizedOptions.tokenBudget,
|
|
446
479
|
tokensUsed: 0,
|
|
480
|
+
usageTrackers: {},
|
|
447
481
|
timeUsedSeconds: 0,
|
|
448
482
|
createdAt: now,
|
|
449
483
|
updatedAt: now,
|
|
@@ -605,14 +639,66 @@ async function clearGoal(sessionID) {
|
|
|
605
639
|
return existed;
|
|
606
640
|
});
|
|
607
641
|
}
|
|
608
|
-
async function accountUsage(sessionID, tokensUsed) {
|
|
642
|
+
async function accountUsage(sessionID, tokensUsed, options) {
|
|
609
643
|
return mutate((state) => {
|
|
610
644
|
const goal = state.goals[sessionID];
|
|
611
645
|
if (!goal)
|
|
612
646
|
return null;
|
|
613
647
|
accountWallClock(goal);
|
|
614
648
|
if (typeof tokensUsed === "number" && Number.isFinite(tokensUsed)) {
|
|
615
|
-
|
|
649
|
+
const observed = Math.max(0, Math.ceil(tokensUsed));
|
|
650
|
+
if (options?.cumulative === true) {
|
|
651
|
+
const source = options.source?.trim() || "default";
|
|
652
|
+
let tracker = goal.usageTrackers[source];
|
|
653
|
+
if (!tracker) {
|
|
654
|
+
const initialBaseline = nonNegativeIntegerOrNull(options.initialBaseline);
|
|
655
|
+
tracker = initialBaseline != null && initialBaseline <= observed ? {
|
|
656
|
+
baseline: initialBaseline,
|
|
657
|
+
lastObserved: observed,
|
|
658
|
+
baseTokens: goal.tokensUsed,
|
|
659
|
+
pendingBaseline: null,
|
|
660
|
+
pendingBaseTokens: null
|
|
661
|
+
} : {
|
|
662
|
+
baseline: observed,
|
|
663
|
+
lastObserved: observed,
|
|
664
|
+
baseTokens: goal.tokensUsed,
|
|
665
|
+
pendingBaseline: null,
|
|
666
|
+
pendingBaseTokens: null
|
|
667
|
+
};
|
|
668
|
+
goal.usageTrackers[source] = tracker;
|
|
669
|
+
} else if (observed < tracker.lastObserved) {
|
|
670
|
+
const initialBaseline = nonNegativeIntegerOrNull(options.initialBaseline);
|
|
671
|
+
if (initialBaseline != null && initialBaseline <= observed) {
|
|
672
|
+
tracker = {
|
|
673
|
+
baseline: initialBaseline,
|
|
674
|
+
lastObserved: observed,
|
|
675
|
+
baseTokens: goal.tokensUsed,
|
|
676
|
+
pendingBaseline: null,
|
|
677
|
+
pendingBaseTokens: null
|
|
678
|
+
};
|
|
679
|
+
goal.usageTrackers[source] = tracker;
|
|
680
|
+
} else if (tracker.pendingBaseline == null || observed < tracker.pendingBaseline) {
|
|
681
|
+
tracker.pendingBaseline = observed;
|
|
682
|
+
tracker.pendingBaseTokens = goal.tokensUsed;
|
|
683
|
+
} else {
|
|
684
|
+
tracker = {
|
|
685
|
+
baseline: tracker.pendingBaseline,
|
|
686
|
+
lastObserved: observed,
|
|
687
|
+
baseTokens: tracker.pendingBaseTokens ?? goal.tokensUsed,
|
|
688
|
+
pendingBaseline: null,
|
|
689
|
+
pendingBaseTokens: null
|
|
690
|
+
};
|
|
691
|
+
goal.usageTrackers[source] = tracker;
|
|
692
|
+
}
|
|
693
|
+
} else {
|
|
694
|
+
tracker.lastObserved = observed;
|
|
695
|
+
tracker.pendingBaseline = null;
|
|
696
|
+
tracker.pendingBaseTokens = null;
|
|
697
|
+
}
|
|
698
|
+
goal.tokensUsed = Math.max(goal.tokensUsed, tracker.baseTokens + observed - tracker.baseline);
|
|
699
|
+
} else {
|
|
700
|
+
goal.tokensUsed = Math.max(goal.tokensUsed, observed);
|
|
701
|
+
}
|
|
616
702
|
}
|
|
617
703
|
maybeStopForBudget(goal);
|
|
618
704
|
goal.updatedAt = nowSeconds();
|
|
@@ -1058,6 +1144,9 @@ var NON_PROGRESS_TOOLS = new Set(["get_goal", "get_goal_history"]);
|
|
|
1058
1144
|
var TASK_TERMINAL_STATES = new Set(["completed", "error", "cancelled"]);
|
|
1059
1145
|
var PLAN_MODE_CREATE_NOTICE = 'Goal recorded while the session is in Plan mode, so execution is paused. Do not start implementation work now. Ask the user to switch to Build mode and resume the goal (for example with "/goal resume") to begin execution.';
|
|
1060
1146
|
var LIMITED_GOAL_NOTICE = "Safety limit reached. Do not start or continue substantive work for this goal. Summarize useful progress, remaining work, and blockers, then wait for the user to resume or edit the goal.";
|
|
1147
|
+
var DUPLICATE_GOAL_NOTICE = "This non-closed goal already exists. Do not call create_goal or set_goal again. The existing objective and limits were preserved; repeated-call arguments were not applied. Use the returned goal state and continue only when its status permits execution.";
|
|
1148
|
+
var CONFLICTING_GOAL_NOTICE = "A different non-closed goal already exists. Do not call create_goal or set_goal again. Report the conflict instead of replacing the goal; edit, clear, complete, or mark it unmet only when explicitly requested.";
|
|
1149
|
+
var RESTRICTED_GOAL_NOTICE = "Goal execution is not allowed from the current restricted agent or while the goal is paused for Plan mode. Switch to Build mode and resume the goal before doing substantive work.";
|
|
1061
1150
|
var activeContinuations = new Set;
|
|
1062
1151
|
function restrictedAgentSet(options) {
|
|
1063
1152
|
if (options?.allow_goal_execution_from_plan === true)
|
|
@@ -1084,9 +1173,9 @@ Use the goal tools to handle this command:
|
|
|
1084
1173
|
- If the arguments start with "edit ", update the current goal objective by calling update_goal_objective with the remaining text.
|
|
1085
1174
|
- If the arguments start with "complete " or "done ", perform a completion audit against real artifacts and command output. Call update_goal with status "complete" only if the goal is achieved, using concise evidence from the audit.
|
|
1086
1175
|
- If the arguments start with "unmet ", "blocked ", or "blocker ", call update_goal with status "unmet" only when the goal cannot be achieved or needs external input, using the remaining arguments as the blocker.
|
|
1087
|
-
- Otherwise,
|
|
1176
|
+
- Otherwise, call get_goal first. If it returns a non-closed goal with the same objective, do not create it again; continue working from the returned state. If it returns a different non-closed goal, report that conflict instead of replacing it. Only when there is no non-closed goal, call create_goal once. Use the full arguments as the objective. If the user includes explicit budget instructions, pass token_budget, max_auto_turns, or max_duration_seconds to create_goal rather than leaving those words in the objective.
|
|
1088
1177
|
|
|
1089
|
-
Create a goal only from these explicit command arguments. Do not infer a goal from unrelated session context. After create_goal succeeds, continue working
|
|
1178
|
+
Create a goal only from these explicit command arguments. Do not infer a goal from unrelated session context. After create_goal succeeds or returns an existing matching goal, never call it again for this command; continue working from the returned goal state.`;
|
|
1090
1179
|
}
|
|
1091
1180
|
function commandNameFromOptions(options) {
|
|
1092
1181
|
const name = options?.command_name?.trim() || DEFAULT_COMMAND_NAME;
|
|
@@ -1097,7 +1186,7 @@ function commandNameFromOptions(options) {
|
|
|
1097
1186
|
function positiveIntegerOrNull2(value) {
|
|
1098
1187
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
1099
1188
|
}
|
|
1100
|
-
function
|
|
1189
|
+
function nonNegativeIntegerOrNull2(value) {
|
|
1101
1190
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
1102
1191
|
}
|
|
1103
1192
|
function timeoutMillisecondsFromSeconds(value) {
|
|
@@ -1190,9 +1279,9 @@ function outputTokensFromMessage(message) {
|
|
|
1190
1279
|
return outputTokensFromRecord(message.info.tokens);
|
|
1191
1280
|
return;
|
|
1192
1281
|
}
|
|
1193
|
-
function
|
|
1282
|
+
function usageFromMessages(messages) {
|
|
1194
1283
|
const exactTotal = messages.reduce((sum, message) => sum + (exactTokensFromMessage(message) ?? 0), 0);
|
|
1195
|
-
return exactTotal > 0 ? exactTotal : estimateMessages(messages);
|
|
1284
|
+
return exactTotal > 0 ? { tokens: exactTotal, source: "v1.messages.exact" } : { tokens: estimateMessages(messages), source: "v1.messages.estimated" };
|
|
1196
1285
|
}
|
|
1197
1286
|
function taskHeader(output) {
|
|
1198
1287
|
const resultIndex = output.search(/<task_(?:result|error)>/);
|
|
@@ -1687,17 +1776,44 @@ function getGoalToolResult(goal) {
|
|
|
1687
1776
|
}
|
|
1688
1777
|
async function createGoalFromTool(input, context, services) {
|
|
1689
1778
|
const planningOnly = services.isPlanAgent(context.agent);
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1779
|
+
const objective = validateObjective(input.objective);
|
|
1780
|
+
const existing = await getGoal(context.sessionID);
|
|
1781
|
+
if (existing && !isClosedGoal(existing))
|
|
1782
|
+
return existingGoalResult(existing, objective, planningOnly);
|
|
1783
|
+
let goal;
|
|
1784
|
+
try {
|
|
1785
|
+
goal = await createGoal(context.sessionID, input.objective, {
|
|
1786
|
+
tokenBudget: input.token_budget ?? services.options.default_token_budget ?? null,
|
|
1787
|
+
maxAutoTurns: input.max_auto_turns ?? null,
|
|
1788
|
+
maxDurationSeconds: input.max_duration_seconds ?? services.options.max_goal_duration_seconds ?? null,
|
|
1789
|
+
noProgressTokenThreshold: services.options.no_progress_token_threshold ?? null,
|
|
1790
|
+
maxNoProgressTurns: services.options.max_no_progress_turns ?? null,
|
|
1791
|
+
agent: typeof context.agent === "string" ? context.agent : null,
|
|
1792
|
+
initialStatus: planningOnly ? "paused" : "active"
|
|
1793
|
+
});
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
if (!(error instanceof Error) || !error.message.includes("non-closed goal"))
|
|
1796
|
+
throw error;
|
|
1797
|
+
const raced = await getGoal(context.sessionID);
|
|
1798
|
+
if (raced && !isClosedGoal(raced))
|
|
1799
|
+
return existingGoalResult(raced, objective, planningOnly);
|
|
1800
|
+
throw error;
|
|
1801
|
+
}
|
|
1802
|
+
await services.initializeUsage?.(context.sessionID);
|
|
1699
1803
|
return JSON.stringify(planningOnly ? { goal, plan_mode_notice: PLAN_MODE_CREATE_NOTICE } : { goal }, null, 2);
|
|
1700
1804
|
}
|
|
1805
|
+
function isClosedGoal(goal) {
|
|
1806
|
+
return goal.status === "complete" || goal.status === "unmet";
|
|
1807
|
+
}
|
|
1808
|
+
function existingGoalResult(goal, requestedObjective, planningOnly) {
|
|
1809
|
+
const reused = goal.objective === requestedObjective;
|
|
1810
|
+
return JSON.stringify({
|
|
1811
|
+
goal,
|
|
1812
|
+
...reused ? { goal_reused: true, duplicate_goal_notice: DUPLICATE_GOAL_NOTICE } : { goal_conflict: true, goal_conflict_notice: CONFLICTING_GOAL_NOTICE },
|
|
1813
|
+
...goal.status === "budgetLimited" || goal.status === "usageLimited" ? { goal_mode_notice: LIMITED_GOAL_NOTICE } : {},
|
|
1814
|
+
...planningOnly || goal.stopReason === PLAN_MODE_STOP_REASON ? { plan_mode_notice: RESTRICTED_GOAL_NOTICE } : {}
|
|
1815
|
+
}, null, 2);
|
|
1816
|
+
}
|
|
1701
1817
|
async function updateGoalObjectiveFromTool(input, context, services) {
|
|
1702
1818
|
const requested = input.status ?? "active";
|
|
1703
1819
|
const planningOnly = requested === "active" && services.isPlanAgent(context.agent);
|
|
@@ -1758,7 +1874,7 @@ var server = async ({ client }, options) => {
|
|
|
1758
1874
|
const autoContinue = options?.auto_continue ?? true;
|
|
1759
1875
|
const deferWhileTasksActive = options?.defer_while_tasks_active ?? true;
|
|
1760
1876
|
const maxAutoTurns = positiveIntegerOrNull2(options?.max_auto_turns) ?? DEFAULT_MAX_AUTO_TURNS;
|
|
1761
|
-
const minInterval =
|
|
1877
|
+
const minInterval = nonNegativeIntegerOrNull2(options?.min_continue_interval_seconds) ?? DEFAULT_CONTINUE_INTERVAL_SECONDS;
|
|
1762
1878
|
const maxTurnTimeMs = timeoutMillisecondsFromSeconds(options?.max_turn_time);
|
|
1763
1879
|
const maxPromptFailures = positiveIntegerOrNull2(options?.max_prompt_failures) ?? DEFAULT_MAX_PROMPT_FAILURES;
|
|
1764
1880
|
const registerCommand = options?.register_command ?? true;
|
|
@@ -2056,7 +2172,7 @@ var server = async ({ client }, options) => {
|
|
|
2056
2172
|
}
|
|
2057
2173
|
},
|
|
2058
2174
|
create_goal: {
|
|
2059
|
-
description: "Create a goal only when explicitly requested by the user or system/developer instructions; do not infer goals from ordinary tasks.
|
|
2175
|
+
description: "Create a goal only when explicitly requested by the user or system/developer instructions; do not infer goals from ordinary tasks. If any non-closed goal exists, this returns the existing goal as either reused or conflicting and must not be retried. While the session is in Plan mode, the goal is recorded as paused and execution requires the user to switch to Build mode.",
|
|
2060
2176
|
args: {
|
|
2061
2177
|
objective: z.string().min(1).max(4000).describe("The concrete objective to start pursuing."),
|
|
2062
2178
|
token_budget: z.number().int().positive().nullable().optional().describe("Optional positive token budget."),
|
|
@@ -2068,7 +2184,7 @@ var server = async ({ client }, options) => {
|
|
|
2068
2184
|
}
|
|
2069
2185
|
},
|
|
2070
2186
|
set_goal: {
|
|
2071
|
-
description: "Set a new goal when the user explicitly asks the agent to formulate and set its own goal. The model should write the objective itself based on the user's explicit request.
|
|
2187
|
+
description: "Set a new goal when the user explicitly asks the agent to formulate and set its own goal. The model should write the objective itself based on the user's explicit request. If any non-closed goal exists, this returns the existing goal as either reused or conflicting and must not be retried. While the session is in Plan mode, the goal is recorded as paused and execution requires the user to switch to Build mode.",
|
|
2072
2188
|
args: {
|
|
2073
2189
|
objective: z.string().min(1).max(4000).describe("The model-formulated concrete objective to start pursuing."),
|
|
2074
2190
|
token_budget: z.number().int().positive().nullable().optional().describe("Optional positive token budget."),
|
|
@@ -2167,7 +2283,8 @@ var server = async ({ client }, options) => {
|
|
|
2167
2283
|
const sessionID = "sessionID" in input && typeof input.sessionID === "string" ? input.sessionID : output.messages.find((message) => typeof message.info.sessionID === "string")?.info.sessionID;
|
|
2168
2284
|
if (!sessionID)
|
|
2169
2285
|
return;
|
|
2170
|
-
|
|
2286
|
+
const usage = usageFromMessages(output.messages);
|
|
2287
|
+
await accountUsage(sessionID, usage.tokens, { cumulative: true, source: usage.source });
|
|
2171
2288
|
const observed = await recordAssistantMessage(sessionID, latestAssistantMessage(output.messages), options ?? {});
|
|
2172
2289
|
await reconcileLocalMarkerAfterProgress(locallyDeliveredPendingSessions, sessionID, observed.goal);
|
|
2173
2290
|
const scheduled = scheduledContinuations.get(sessionID);
|
|
@@ -2298,7 +2415,7 @@ async function setupV2(context) {
|
|
|
2298
2415
|
const autoContinue = options.auto_continue ?? true;
|
|
2299
2416
|
const deferWhileTasksActive = options.defer_while_tasks_active ?? true;
|
|
2300
2417
|
const maxAutoTurns = positiveIntegerOrNull2(options.max_auto_turns) ?? DEFAULT_MAX_AUTO_TURNS;
|
|
2301
|
-
const minInterval =
|
|
2418
|
+
const minInterval = nonNegativeIntegerOrNull2(options.min_continue_interval_seconds) ?? DEFAULT_CONTINUE_INTERVAL_SECONDS;
|
|
2302
2419
|
const maxTurnTimeMs = timeoutMillisecondsFromSeconds(options.max_turn_time);
|
|
2303
2420
|
const maxPromptFailures = positiveIntegerOrNull2(options.max_prompt_failures) ?? DEFAULT_MAX_PROMPT_FAILURES;
|
|
2304
2421
|
const registerCommand = options.register_command ?? true;
|
|
@@ -2314,11 +2431,21 @@ async function setupV2(context) {
|
|
|
2314
2431
|
const toolAttempts = new Map;
|
|
2315
2432
|
const planAgents = restrictedAgentSet(options);
|
|
2316
2433
|
const isPlanAgent = (agent) => typeof agent === "string" && planAgents.has(agent.trim().toLowerCase());
|
|
2317
|
-
const goalServices = { options, isPlanAgent };
|
|
2318
2434
|
const activeContinuationsV2 = new Set;
|
|
2319
2435
|
const latestStepBySession = new Map;
|
|
2320
2436
|
const stepTextBuffers = new Map;
|
|
2321
2437
|
const stepTokenSums = new Map;
|
|
2438
|
+
const goalServices = {
|
|
2439
|
+
options,
|
|
2440
|
+
isPlanAgent,
|
|
2441
|
+
initializeUsage: async (sessionID) => {
|
|
2442
|
+
try {
|
|
2443
|
+
await accountUsage(sessionID, stepTokenSums.get(sessionID) ?? 0, { cumulative: true, source: "v2.steps" });
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
v2ErrorLog("Failed to initialize goal usage accounting", error);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
};
|
|
2322
2449
|
const registrations = [];
|
|
2323
2450
|
let disposed = false;
|
|
2324
2451
|
function stepKey(sessionID, messageID2) {
|
|
@@ -2719,7 +2846,11 @@ async function setupV2(context) {
|
|
|
2719
2846
|
if (typeof tokens === "number") {
|
|
2720
2847
|
const sum = (stepTokenSums.get(sessionID) ?? 0) + tokens;
|
|
2721
2848
|
stepTokenSums.set(sessionID, sum);
|
|
2722
|
-
await accountUsage(sessionID, sum
|
|
2849
|
+
await accountUsage(sessionID, sum, {
|
|
2850
|
+
cumulative: true,
|
|
2851
|
+
source: "v2.steps",
|
|
2852
|
+
initialBaseline: Math.ceil(sum - tokens)
|
|
2853
|
+
});
|
|
2723
2854
|
}
|
|
2724
2855
|
const text = stepTextBuffers.get(stepKey(sessionID, messageID2)) ?? "";
|
|
2725
2856
|
stepTextBuffers.delete(stepKey(sessionID, messageID2));
|
|
@@ -2755,7 +2886,11 @@ async function setupV2(context) {
|
|
|
2755
2886
|
if (typeof tokens === "number") {
|
|
2756
2887
|
const sum = (stepTokenSums.get(sessionID) ?? 0) + tokens;
|
|
2757
2888
|
stepTokenSums.set(sessionID, sum);
|
|
2758
|
-
await accountUsage(sessionID, sum
|
|
2889
|
+
await accountUsage(sessionID, sum, {
|
|
2890
|
+
cumulative: true,
|
|
2891
|
+
source: "v2.steps",
|
|
2892
|
+
initialBaseline: Math.ceil(sum - tokens)
|
|
2893
|
+
});
|
|
2759
2894
|
}
|
|
2760
2895
|
const text = stepTextBuffers.get(stepKey(sessionID, messageID2)) ?? "";
|
|
2761
2896
|
stepTextBuffers.delete(stepKey(sessionID, messageID2));
|
|
@@ -2788,7 +2923,7 @@ async function setupV2(context) {
|
|
|
2788
2923
|
return;
|
|
2789
2924
|
const tokens = tokensFromRecord(data.tokens);
|
|
2790
2925
|
if (typeof tokens === "number")
|
|
2791
|
-
await accountUsage(sessionID, tokens);
|
|
2926
|
+
await accountUsage(sessionID, tokens, { cumulative: true, source: "v2.session" });
|
|
2792
2927
|
return;
|
|
2793
2928
|
}
|
|
2794
2929
|
}
|
|
@@ -2913,7 +3048,7 @@ function goalToolsV2(services) {
|
|
|
2913
3048
|
},
|
|
2914
3049
|
{
|
|
2915
3050
|
name: "create_goal",
|
|
2916
|
-
description: "Create a goal only when explicitly requested by the user or system/developer instructions; do not infer goals from ordinary tasks.
|
|
3051
|
+
description: "Create a goal only when explicitly requested by the user or system/developer instructions; do not infer goals from ordinary tasks. If any non-closed goal exists, this returns the existing goal as either reused or conflicting and must not be retried. While the session is in Plan mode, the goal is recorded as paused and execution requires the user to switch to Build mode.",
|
|
2917
3052
|
input: v2ObjectSchema({
|
|
2918
3053
|
objective: { type: "string", minLength: 1, maxLength: 4000, description: "The concrete objective to start pursuing." },
|
|
2919
3054
|
token_budget: { type: ["integer", "null"], minimum: 1, description: "Optional positive token budget." },
|
|
@@ -2927,7 +3062,7 @@ function goalToolsV2(services) {
|
|
|
2927
3062
|
},
|
|
2928
3063
|
{
|
|
2929
3064
|
name: "set_goal",
|
|
2930
|
-
description: "Set a new goal when the user explicitly asks the agent to formulate and set its own goal. The model should write the objective itself based on the user's explicit request.
|
|
3065
|
+
description: "Set a new goal when the user explicitly asks the agent to formulate and set its own goal. The model should write the objective itself based on the user's explicit request. If any non-closed goal exists, this returns the existing goal as either reused or conflicting and must not be retried. While the session is in Plan mode, the goal is recorded as paused and execution requires the user to switch to Build mode.",
|
|
2931
3066
|
input: v2ObjectSchema({
|
|
2932
3067
|
objective: {
|
|
2933
3068
|
type: "string",
|
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.37",
|
|
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",
|