@prevalentware/opencode-goal-plugin 0.1.44 → 0.1.45
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 +6 -1
- package/dist/server.js +79 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,7 +128,8 @@ In OpenCode 1, server options use the package-and-options tuple in `opencode.jso
|
|
|
128
128
|
"no_progress_token_threshold": 50,
|
|
129
129
|
"max_no_progress_turns": 2,
|
|
130
130
|
"restricted_agents": ["plan"],
|
|
131
|
-
"allow_goal_execution_from_plan": false
|
|
131
|
+
"allow_goal_execution_from_plan": false,
|
|
132
|
+
"max_objective_chars": 100000
|
|
132
133
|
}
|
|
133
134
|
]
|
|
134
135
|
]
|
|
@@ -169,6 +170,10 @@ Defaults:
|
|
|
169
170
|
- `command_name`: `"goal"`; renames the main goal command only. The reserved names `pause_goal` and `resume_goal` fall back to `goal` so the standalone controls remain available.
|
|
170
171
|
- `restricted_agents`: `["plan"]`; agents (matched case-insensitively) treated as planning-only for goal execution.
|
|
171
172
|
- `allow_goal_execution_from_plan`: `false`; when `true`, disables Plan-mode goal restrictions entirely.
|
|
173
|
+
- `max_objective_chars`: `100000`; maximum Unicode code-point length of the submitted goal objective, completion evidence,
|
|
174
|
+
and blocker text. The previous 4000-character cap was a defect, not a compatibility constraint. The same limit is
|
|
175
|
+
advertised on V1 and V2 tool schemas and enforced at runtime, independently per plugin instance. Accepted values are
|
|
176
|
+
trimmed before persistence. Large objectives are echoed into continuation and compaction prompts.
|
|
172
177
|
|
|
173
178
|
## Goal Workflow
|
|
174
179
|
|
package/dist/server.js
CHANGED
|
@@ -370,21 +370,23 @@ async function mutate(fn) {
|
|
|
370
370
|
}));
|
|
371
371
|
});
|
|
372
372
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const value = evidence?.trim();
|
|
383
|
-
if (!value)
|
|
373
|
+
var DEFAULT_MAX_OBJECTIVE_CHARS = 1e5;
|
|
374
|
+
function resolveMaxObjectiveChars(value) {
|
|
375
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : DEFAULT_MAX_OBJECTIVE_CHARS;
|
|
376
|
+
}
|
|
377
|
+
function boundedText(value, limit, label) {
|
|
378
|
+
if ([...value].length > limit)
|
|
379
|
+
throw new Error(`${label} must be at most ${limit} characters`);
|
|
380
|
+
const trimmed = value.trim();
|
|
381
|
+
if (!trimmed)
|
|
384
382
|
throw new Error(`${label} must not be empty`);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
383
|
+
return trimmed;
|
|
384
|
+
}
|
|
385
|
+
function validateObjective(objective, limit = DEFAULT_MAX_OBJECTIVE_CHARS) {
|
|
386
|
+
return boundedText(objective, limit, "goal objective");
|
|
387
|
+
}
|
|
388
|
+
function validateEvidence(evidence, label, limit = DEFAULT_MAX_OBJECTIVE_CHARS) {
|
|
389
|
+
return boundedText(evidence ?? "", limit, label);
|
|
388
390
|
}
|
|
389
391
|
function normalizeState(state) {
|
|
390
392
|
for (const goal of Object.values(state.goals))
|
|
@@ -460,7 +462,8 @@ function normalizeCreateOptions(input) {
|
|
|
460
462
|
noProgressTokenThreshold: DEFAULT_NO_PROGRESS_TOKEN_THRESHOLD,
|
|
461
463
|
maxNoProgressTurns: DEFAULT_MAX_NO_PROGRESS_TURNS,
|
|
462
464
|
agent: null,
|
|
463
|
-
initialStatus: "active"
|
|
465
|
+
initialStatus: "active",
|
|
466
|
+
maxObjectiveChars: DEFAULT_MAX_OBJECTIVE_CHARS
|
|
464
467
|
};
|
|
465
468
|
}
|
|
466
469
|
return {
|
|
@@ -470,7 +473,8 @@ function normalizeCreateOptions(input) {
|
|
|
470
473
|
noProgressTokenThreshold: positiveIntegerOrNull(input?.noProgressTokenThreshold) ?? DEFAULT_NO_PROGRESS_TOKEN_THRESHOLD,
|
|
471
474
|
maxNoProgressTurns: positiveIntegerOrNull(input?.maxNoProgressTurns) ?? DEFAULT_MAX_NO_PROGRESS_TURNS,
|
|
472
475
|
agent: typeof input?.agent === "string" && input.agent.trim() ? input.agent.trim() : null,
|
|
473
|
-
initialStatus: input?.initialStatus === "paused" ? "paused" : "active"
|
|
476
|
+
initialStatus: input?.initialStatus === "paused" ? "paused" : "active",
|
|
477
|
+
maxObjectiveChars: resolveMaxObjectiveChars(input?.maxObjectiveChars)
|
|
474
478
|
};
|
|
475
479
|
}
|
|
476
480
|
function positiveIntegerOrNull(value) {
|
|
@@ -570,8 +574,8 @@ async function getGoalInternal(sessionID) {
|
|
|
570
574
|
return goal ? snapshotInternal(goal) : null;
|
|
571
575
|
}
|
|
572
576
|
async function createGoal(sessionID, objective, options) {
|
|
573
|
-
const value = validateObjective(objective);
|
|
574
577
|
const normalizedOptions = normalizeCreateOptions(options);
|
|
578
|
+
const value = validateObjective(objective, resolveMaxObjectiveChars(normalizedOptions.maxObjectiveChars));
|
|
575
579
|
return mutate((state) => {
|
|
576
580
|
const existing = state.goals[sessionID];
|
|
577
581
|
if (existing && !isClosed(existing.status)) {
|
|
@@ -623,7 +627,7 @@ async function createGoal(sessionID, objective, options) {
|
|
|
623
627
|
});
|
|
624
628
|
}
|
|
625
629
|
async function updateGoalObjective(sessionID, objective, status = "active", options) {
|
|
626
|
-
const value = validateObjective(objective);
|
|
630
|
+
const value = validateObjective(objective, resolveMaxObjectiveChars(options?.maxObjectiveChars));
|
|
627
631
|
const agent = typeof options?.agent === "string" && options.agent.trim() ? options.agent.trim() : null;
|
|
628
632
|
const planModePause = options?.planModePause === true;
|
|
629
633
|
return mutate((state) => {
|
|
@@ -714,7 +718,8 @@ async function setGoalStatus(sessionID, status, agent) {
|
|
|
714
718
|
return snapshot(goal);
|
|
715
719
|
});
|
|
716
720
|
}
|
|
717
|
-
async function closeGoal(sessionID, input) {
|
|
721
|
+
async function closeGoal(sessionID, input, maxObjectiveChars = DEFAULT_MAX_OBJECTIVE_CHARS) {
|
|
722
|
+
const limit = resolveMaxObjectiveChars(maxObjectiveChars);
|
|
718
723
|
return mutate((state) => {
|
|
719
724
|
const goal = state.goals[sessionID];
|
|
720
725
|
if (!goal)
|
|
@@ -727,12 +732,12 @@ async function closeGoal(sessionID, input) {
|
|
|
727
732
|
goal.lastAccountedAt = null;
|
|
728
733
|
goal.stopReason = input.status === "complete" ? null : "blocked";
|
|
729
734
|
if (input.status === "complete") {
|
|
730
|
-
goal.completionEvidence = validateEvidence(input.evidence, "completion evidence");
|
|
735
|
+
goal.completionEvidence = validateEvidence(input.evidence, "completion evidence", limit);
|
|
731
736
|
goal.blocker = null;
|
|
732
737
|
goal.lastStatus = "Goal completed.";
|
|
733
738
|
pushHistory(goal, "completed", goal.completionEvidence);
|
|
734
739
|
} else {
|
|
735
|
-
goal.blocker = validateEvidence(input.blocker, "blocker");
|
|
740
|
+
goal.blocker = validateEvidence(input.blocker, "blocker", limit);
|
|
736
741
|
goal.completionEvidence = null;
|
|
737
742
|
goal.lastStatus = "Goal marked unmet.";
|
|
738
743
|
pushHistory(goal, "unmet", goal.blocker);
|
|
@@ -740,11 +745,11 @@ async function closeGoal(sessionID, input) {
|
|
|
740
745
|
return snapshot(goal);
|
|
741
746
|
});
|
|
742
747
|
}
|
|
743
|
-
async function completeGoal(sessionID, evidence) {
|
|
744
|
-
return closeGoal(sessionID, { status: "complete", evidence });
|
|
748
|
+
async function completeGoal(sessionID, evidence, maxObjectiveChars = DEFAULT_MAX_OBJECTIVE_CHARS) {
|
|
749
|
+
return closeGoal(sessionID, { status: "complete", evidence }, maxObjectiveChars);
|
|
745
750
|
}
|
|
746
|
-
async function markGoalUnmet(sessionID, blocker) {
|
|
747
|
-
return closeGoal(sessionID, { status: "unmet", blocker });
|
|
751
|
+
async function markGoalUnmet(sessionID, blocker, maxObjectiveChars = DEFAULT_MAX_OBJECTIVE_CHARS) {
|
|
752
|
+
return closeGoal(sessionID, { status: "unmet", blocker }, maxObjectiveChars);
|
|
748
753
|
}
|
|
749
754
|
async function clearGoal(sessionID) {
|
|
750
755
|
return mutate((state) => {
|
|
@@ -1269,6 +1274,15 @@ function restrictedAgentSet(options) {
|
|
|
1269
1274
|
return new Set(names.map((name) => typeof name === "string" ? name.trim().toLowerCase() : "").filter(Boolean));
|
|
1270
1275
|
}
|
|
1271
1276
|
function goalCommandTemplate(commandName) {
|
|
1277
|
+
const createGuidance = [
|
|
1278
|
+
"Otherwise, call get_goal first.",
|
|
1279
|
+
"If it returns a non-closed goal with the same objective, do not create it again; " + "continue working from the returned state.",
|
|
1280
|
+
"If it returns a different non-closed goal, report that conflict instead of replacing it.",
|
|
1281
|
+
"Only when there is no non-closed goal, call create_goal once.",
|
|
1282
|
+
"Build the objective as a complete, faithful representation of the arguments: keep every requirement, constraint, " + "scope boundary, and success criterion with no omissions or loss of meaning.",
|
|
1283
|
+
"You may restructure and rephrase for clarity and coherence, but do NOT compress, truncate, or drop any content, " + "and do NOT substitute the content with references or pointers to external files.",
|
|
1284
|
+
"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."
|
|
1285
|
+
].join(" ");
|
|
1272
1286
|
return `OpenCode goal mode command "/${commandName}" was invoked.
|
|
1273
1287
|
|
|
1274
1288
|
Arguments:
|
|
@@ -1287,7 +1301,7 @@ Use the goal tools to handle this command:
|
|
|
1287
1301
|
- If the arguments start with "edit ", update the current goal objective by calling update_goal_objective with the remaining text.
|
|
1288
1302
|
- 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.
|
|
1289
1303
|
- 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.
|
|
1290
|
-
-
|
|
1304
|
+
- ${createGuidance}
|
|
1291
1305
|
|
|
1292
1306
|
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.`;
|
|
1293
1307
|
}
|
|
@@ -1949,9 +1963,24 @@ function getGoalToolResult(goal) {
|
|
|
1949
1963
|
}
|
|
1950
1964
|
return JSON.stringify(result, null, 2);
|
|
1951
1965
|
}
|
|
1966
|
+
function boundedGoalTextSchema(limit, description, validate) {
|
|
1967
|
+
return z.string().superRefine((value, ctx) => {
|
|
1968
|
+
try {
|
|
1969
|
+
validate(value);
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
ctx.addIssue({
|
|
1972
|
+
code: "custom",
|
|
1973
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
}).meta({ minLength: 1, maxLength: limit, pattern: "\\S", description });
|
|
1977
|
+
}
|
|
1978
|
+
function v2GoalTextSchema(limit, description) {
|
|
1979
|
+
return { type: "string", minLength: 1, maxLength: limit, pattern: "\\S", description };
|
|
1980
|
+
}
|
|
1952
1981
|
async function createGoalFromTool(input, context, services) {
|
|
1953
1982
|
const planningOnly = services.isPlanAgent(context.agent);
|
|
1954
|
-
const objective = validateObjective(input.objective);
|
|
1983
|
+
const objective = validateObjective(input.objective, services.maxObjectiveChars);
|
|
1955
1984
|
const existing = await getGoal(context.sessionID);
|
|
1956
1985
|
if (existing && !isClosedGoal(existing))
|
|
1957
1986
|
return existingGoalResult(existing, objective, planningOnly);
|
|
@@ -1964,7 +1993,8 @@ async function createGoalFromTool(input, context, services) {
|
|
|
1964
1993
|
noProgressTokenThreshold: services.options.no_progress_token_threshold ?? null,
|
|
1965
1994
|
maxNoProgressTurns: services.options.max_no_progress_turns ?? null,
|
|
1966
1995
|
agent: typeof context.agent === "string" ? context.agent : null,
|
|
1967
|
-
initialStatus: planningOnly ? "paused" : "active"
|
|
1996
|
+
initialStatus: planningOnly ? "paused" : "active",
|
|
1997
|
+
maxObjectiveChars: services.maxObjectiveChars
|
|
1968
1998
|
});
|
|
1969
1999
|
} catch (error) {
|
|
1970
2000
|
if (!(error instanceof Error) || !error.message.includes("non-closed goal"))
|
|
@@ -1994,18 +2024,19 @@ async function updateGoalObjectiveFromTool(input, context, services) {
|
|
|
1994
2024
|
const planningOnly = requested === "active" && services.isPlanAgent(context.agent);
|
|
1995
2025
|
const goal = await updateGoalObjective(context.sessionID, input.objective, planningOnly ? "paused" : requested, {
|
|
1996
2026
|
agent: typeof context.agent === "string" ? context.agent : null,
|
|
1997
|
-
planModePause: planningOnly
|
|
2027
|
+
planModePause: planningOnly,
|
|
2028
|
+
maxObjectiveChars: services.maxObjectiveChars
|
|
1998
2029
|
});
|
|
1999
2030
|
return JSON.stringify(planningOnly ? { goal, plan_mode_notice: PLAN_MODE_CREATE_NOTICE } : { goal }, null, 2);
|
|
2000
2031
|
}
|
|
2001
|
-
async function closeGoalFromTool(input, context) {
|
|
2032
|
+
async function closeGoalFromTool(input, context, services) {
|
|
2002
2033
|
if (input.status === "complete") {
|
|
2003
|
-
const goal = await completeGoal(context.sessionID, input.evidence ?? "");
|
|
2034
|
+
const goal = await completeGoal(context.sessionID, input.evidence ?? "", services.maxObjectiveChars);
|
|
2004
2035
|
const budget = goal.tokenBudget == null ? "" : ` Token usage: ${goal.tokensUsed}/${goal.tokenBudget}.`;
|
|
2005
2036
|
const report = `Goal achieved. Time used: ${goal.timeUsedSeconds} seconds.${budget} Evidence: ${goal.completionEvidence}.`;
|
|
2006
2037
|
return JSON.stringify({ goal, completion_report: report }, null, 2);
|
|
2007
2038
|
}
|
|
2008
|
-
const goal = await markGoalUnmet(context.sessionID, input.blocker ?? "");
|
|
2039
|
+
const goal = await markGoalUnmet(context.sessionID, input.blocker ?? "", services.maxObjectiveChars);
|
|
2009
2040
|
const report = `Goal unmet. Time used: ${goal.timeUsedSeconds} seconds. Blocker: ${goal.blocker}.`;
|
|
2010
2041
|
return JSON.stringify({ goal, unmet_report: report }, null, 2);
|
|
2011
2042
|
}
|
|
@@ -2069,6 +2100,7 @@ var server = async ({ client }, options) => {
|
|
|
2069
2100
|
const maxPromptFailures = positiveIntegerOrNull2(options?.max_prompt_failures) ?? DEFAULT_MAX_PROMPT_FAILURES;
|
|
2070
2101
|
const registerCommand = options?.register_command ?? true;
|
|
2071
2102
|
const commandName = commandNameFromOptions(options);
|
|
2103
|
+
const objectiveChars = resolveMaxObjectiveChars(options?.max_objective_chars);
|
|
2072
2104
|
const taskTracker = new TaskTracker;
|
|
2073
2105
|
const taskDeferredSessions = new Set;
|
|
2074
2106
|
const scheduledContinuations = new Map;
|
|
@@ -2080,7 +2112,7 @@ var server = async ({ client }, options) => {
|
|
|
2080
2112
|
const watchdogRescuedSessions = new Set;
|
|
2081
2113
|
const planAgents = restrictedAgentSet(options);
|
|
2082
2114
|
const isPlanAgent = (agent) => typeof agent === "string" && planAgents.has(agent.trim().toLowerCase());
|
|
2083
|
-
const goalServices = { options: options ?? {}, isPlanAgent };
|
|
2115
|
+
const goalServices = { options: options ?? {}, isPlanAgent, maxObjectiveChars: objectiveChars };
|
|
2084
2116
|
const stopStateRecoveryReporting = onStateRecovery(statePath(), async ({ stateFile, quarantineFile, outcome, error }) => {
|
|
2085
2117
|
await client.app?.log?.({
|
|
2086
2118
|
body: {
|
|
@@ -2382,7 +2414,7 @@ var server = async ({ client }, options) => {
|
|
|
2382
2414
|
create_goal: {
|
|
2383
2415
|
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.",
|
|
2384
2416
|
args: {
|
|
2385
|
-
objective:
|
|
2417
|
+
objective: boundedGoalTextSchema(objectiveChars, "The concrete objective to start pursuing.", (value) => validateObjective(value, objectiveChars)),
|
|
2386
2418
|
token_budget: z.number().int().positive().nullable().optional().describe("Optional positive token budget."),
|
|
2387
2419
|
max_auto_turns: z.number().int().positive().nullable().optional().describe("Optional per-goal auto-continue limit."),
|
|
2388
2420
|
max_duration_seconds: z.number().int().positive().nullable().optional().describe("Optional per-goal duration limit.")
|
|
@@ -2394,7 +2426,7 @@ var server = async ({ client }, options) => {
|
|
|
2394
2426
|
set_goal: {
|
|
2395
2427
|
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.",
|
|
2396
2428
|
args: {
|
|
2397
|
-
objective:
|
|
2429
|
+
objective: boundedGoalTextSchema(objectiveChars, "The model-formulated concrete objective to start pursuing.", (value) => validateObjective(value, objectiveChars)),
|
|
2398
2430
|
token_budget: z.number().int().positive().nullable().optional().describe("Optional positive token budget."),
|
|
2399
2431
|
max_auto_turns: z.number().int().positive().nullable().optional().describe("Optional per-goal auto-continue limit."),
|
|
2400
2432
|
max_duration_seconds: z.number().int().positive().nullable().optional().describe("Optional per-goal duration limit.")
|
|
@@ -2406,7 +2438,7 @@ var server = async ({ client }, options) => {
|
|
|
2406
2438
|
update_goal_objective: {
|
|
2407
2439
|
description: "Edit the current OpenCode goal objective when the user explicitly asks to edit or replace it.",
|
|
2408
2440
|
args: {
|
|
2409
|
-
objective:
|
|
2441
|
+
objective: boundedGoalTextSchema(objectiveChars, "The updated concrete objective.", (value) => validateObjective(value, objectiveChars)),
|
|
2410
2442
|
status: z.enum(["active", "paused"]).optional().describe("Whether the edited goal should be active or paused.")
|
|
2411
2443
|
},
|
|
2412
2444
|
async execute(args, context) {
|
|
@@ -2417,11 +2449,11 @@ var server = async ({ client }, options) => {
|
|
|
2417
2449
|
description: "Close the existing goal only after an audit against real evidence. Use status complete only when the objective is achieved and no required work remains, and include evidence. Use status unmet only when the objective cannot be achieved or is blocked, and include the blocker. Do not close a goal merely because work is stopping.",
|
|
2418
2450
|
args: {
|
|
2419
2451
|
status: z.enum(["complete", "unmet"]).describe("Required. complete means achieved; unmet means blocked or impossible."),
|
|
2420
|
-
evidence:
|
|
2421
|
-
blocker:
|
|
2452
|
+
evidence: boundedGoalTextSchema(objectiveChars, "Required when status is complete. Summarize the concrete evidence verified.", (value) => validateEvidence(value, "completion evidence", objectiveChars)).optional(),
|
|
2453
|
+
blocker: boundedGoalTextSchema(objectiveChars, "Required when status is unmet. Explain the concrete blocker or impossibility.", (value) => validateEvidence(value, "blocker", objectiveChars)).optional()
|
|
2422
2454
|
},
|
|
2423
2455
|
async execute(args, context) {
|
|
2424
|
-
return closeGoalFromTool(args, context);
|
|
2456
|
+
return closeGoalFromTool(args, context, goalServices);
|
|
2425
2457
|
}
|
|
2426
2458
|
},
|
|
2427
2459
|
update_goal_status: {
|
|
@@ -2642,6 +2674,7 @@ async function setupV2(context) {
|
|
|
2642
2674
|
const maxPromptFailures = positiveIntegerOrNull2(options.max_prompt_failures) ?? DEFAULT_MAX_PROMPT_FAILURES;
|
|
2643
2675
|
const registerCommand = options.register_command ?? true;
|
|
2644
2676
|
const commandName = commandNameFromOptions(options);
|
|
2677
|
+
const objectiveChars = resolveMaxObjectiveChars(options.max_objective_chars);
|
|
2645
2678
|
const taskTracker = new TaskTracker;
|
|
2646
2679
|
const taskDeferredSessions = new Set;
|
|
2647
2680
|
const scheduledContinuations = new Map;
|
|
@@ -2659,6 +2692,7 @@ async function setupV2(context) {
|
|
|
2659
2692
|
const stepTokenSums = new Map;
|
|
2660
2693
|
const goalServices = {
|
|
2661
2694
|
options,
|
|
2695
|
+
maxObjectiveChars: objectiveChars,
|
|
2662
2696
|
isPlanAgent,
|
|
2663
2697
|
initializeUsage: async (sessionID) => {
|
|
2664
2698
|
try {
|
|
@@ -3327,7 +3361,7 @@ function goalToolsV2(services) {
|
|
|
3327
3361
|
name: "create_goal",
|
|
3328
3362
|
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.",
|
|
3329
3363
|
input: v2ObjectSchema({
|
|
3330
|
-
objective:
|
|
3364
|
+
objective: v2GoalTextSchema(services.maxObjectiveChars, "The concrete objective to start pursuing."),
|
|
3331
3365
|
token_budget: { type: ["integer", "null"], minimum: 1, description: "Optional positive token budget." },
|
|
3332
3366
|
max_auto_turns: { type: ["integer", "null"], minimum: 1, description: "Optional per-goal auto-continue limit." },
|
|
3333
3367
|
max_duration_seconds: { type: ["integer", "null"], minimum: 1, description: "Optional per-goal duration limit." }
|
|
@@ -3341,12 +3375,7 @@ function goalToolsV2(services) {
|
|
|
3341
3375
|
name: "set_goal",
|
|
3342
3376
|
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.",
|
|
3343
3377
|
input: v2ObjectSchema({
|
|
3344
|
-
objective:
|
|
3345
|
-
type: "string",
|
|
3346
|
-
minLength: 1,
|
|
3347
|
-
maxLength: 4000,
|
|
3348
|
-
description: "The model-formulated concrete objective to start pursuing."
|
|
3349
|
-
},
|
|
3378
|
+
objective: v2GoalTextSchema(services.maxObjectiveChars, "The model-formulated concrete objective to start pursuing."),
|
|
3350
3379
|
token_budget: { type: ["integer", "null"], minimum: 1, description: "Optional positive token budget." },
|
|
3351
3380
|
max_auto_turns: { type: ["integer", "null"], minimum: 1, description: "Optional per-goal auto-continue limit." },
|
|
3352
3381
|
max_duration_seconds: { type: ["integer", "null"], minimum: 1, description: "Optional per-goal duration limit." }
|
|
@@ -3360,7 +3389,7 @@ function goalToolsV2(services) {
|
|
|
3360
3389
|
name: "update_goal_objective",
|
|
3361
3390
|
description: "Edit the current OpenCode goal objective when the user explicitly asks to edit or replace it.",
|
|
3362
3391
|
input: v2ObjectSchema({
|
|
3363
|
-
objective:
|
|
3392
|
+
objective: v2GoalTextSchema(services.maxObjectiveChars, "The updated concrete objective."),
|
|
3364
3393
|
status: { type: "string", enum: ["active", "paused"], description: "Whether the edited goal should be active or paused." }
|
|
3365
3394
|
}, ["objective"]),
|
|
3366
3395
|
options: { codemode: false },
|
|
@@ -3377,22 +3406,12 @@ function goalToolsV2(services) {
|
|
|
3377
3406
|
enum: ["complete", "unmet"],
|
|
3378
3407
|
description: "Required. complete means achieved; unmet means blocked or impossible."
|
|
3379
3408
|
},
|
|
3380
|
-
evidence:
|
|
3381
|
-
|
|
3382
|
-
minLength: 1,
|
|
3383
|
-
maxLength: 4000,
|
|
3384
|
-
description: "Required when status is complete. Summarize the concrete evidence verified."
|
|
3385
|
-
},
|
|
3386
|
-
blocker: {
|
|
3387
|
-
type: "string",
|
|
3388
|
-
minLength: 1,
|
|
3389
|
-
maxLength: 4000,
|
|
3390
|
-
description: "Required when status is unmet. Explain the concrete blocker or impossibility."
|
|
3391
|
-
}
|
|
3409
|
+
evidence: v2GoalTextSchema(services.maxObjectiveChars, "Required when status is complete. Summarize the concrete evidence verified."),
|
|
3410
|
+
blocker: v2GoalTextSchema(services.maxObjectiveChars, "Required when status is unmet. Explain the concrete blocker or impossibility.")
|
|
3392
3411
|
}, ["status"]),
|
|
3393
3412
|
options: { codemode: false },
|
|
3394
3413
|
execute: async (args, toolContext) => ({
|
|
3395
|
-
content: await closeGoalFromTool(args, toolContext)
|
|
3414
|
+
content: await closeGoalFromTool(args, toolContext, services)
|
|
3396
3415
|
})
|
|
3397
3416
|
},
|
|
3398
3417
|
{
|
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.45",
|
|
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",
|