@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 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
- function validateObjective(objective) {
374
- const value = objective.trim();
375
- if (!value)
376
- throw new Error("goal objective must not be empty");
377
- if ([...value].length > 4000)
378
- throw new Error("goal objective must be at most 4000 characters");
379
- return value;
380
- }
381
- function validateEvidence(evidence, label) {
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
- if ([...value].length > 4000)
386
- throw new Error(`${label} must be at most 4000 characters`);
387
- return value;
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
- - 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.
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: z.string().min(1).max(4000).describe("The concrete objective to start pursuing."),
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: z.string().min(1).max(4000).describe("The model-formulated concrete objective to start pursuing."),
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: z.string().min(1).max(4000).describe("The updated concrete 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: z.string().min(1).max(4000).optional().describe("Required when status is complete. Summarize the concrete evidence verified."),
2421
- blocker: z.string().min(1).max(4000).optional().describe("Required when status is unmet. Explain the concrete blocker or impossibility.")
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: { type: "string", minLength: 1, maxLength: 4000, description: "The concrete objective to start pursuing." },
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: { type: "string", minLength: 1, maxLength: 4000, description: "The updated concrete 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
- type: "string",
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.44",
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",