@prevalentware/opencode-goal-plugin 0.1.55 → 0.1.56

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
@@ -259,6 +259,8 @@ Ordinary fsync improves crash consistency but is not `F_FULLFSYNC`, so sudden po
259
259
 
260
260
  The plugin migrates version 1 state files to version 2 on the next write. Version 2 adds stable goal identities, the terminal `cancelled` status, and compact archives. Older plugin versions intentionally reject version 2 instead of silently dropping archived history on their next write; back up or isolate `OPENCODE_GOAL_STATE_PATH` before downgrading.
261
261
 
262
+ Planless version 3 files from the planning development build are also compatible: empty `plan: null` and `planRevision: 0` metadata survives reads, updates, and archival, and writes retain version 3. This prevents an otherwise valid shared state file from breaking ordinary OpenCode tool calls in sessions without a goal. Populated plans, nonzero planning revisions, and unknown future versions remain rejected without rewriting the file; use the corresponding planning build or an isolated `OPENCODE_GOAL_STATE_PATH` for those files.
263
+
262
264
  If the rename succeeds but syncing the parent directory reports a genuine I/O error, the mutation reports a write failure even though the new valid state may already be present. This avoids claiming durability that the filesystem did not confirm.
263
265
 
264
266
  If a non-empty state file contains only whitespace, a UTF-8 BOM, or NUL bytes after an interrupted write, the next mutation preserves its exact contents beside the state file as `goals.json.corrupt-<timestamp>-<uuid>` before writing recovered state. If another process replaces the state during recovery, the mutation refuses to overwrite that newer content. If the quarantine copy itself cannot be created, the plugin reports the failure and continues recovery rather than making every prompt fail indefinitely. OpenCode 1 also records the quarantine outcome and path through its application log so the data-loss event remains discoverable.
package/dist/server.js CHANGED
@@ -179,6 +179,8 @@ var GoalSchema = Schema.Struct({
179
179
  id: Schema.optionalWith(Schema.String, { default: () => "" }),
180
180
  sessionID: Schema.String,
181
181
  objective: Schema.String,
182
+ plan: Schema.optional(Schema.Null),
183
+ planRevision: Schema.optional(Schema.Literal(0)),
182
184
  status: Schema.Literal("active", "paused", "budgetLimited", "usageLimited", "complete", "unmet", "cancelled"),
183
185
  tokenBudget: NullableNumber,
184
186
  tokensUsed: Schema.Number,
@@ -216,6 +218,8 @@ var ArchivedGoalSchema = Schema.Struct({
216
218
  id: Schema.String,
217
219
  sessionID: Schema.String,
218
220
  objective: Schema.String,
221
+ plan: Schema.optional(Schema.Null),
222
+ planRevision: Schema.optional(Schema.Literal(0)),
219
223
  status: Schema.Literal("active", "paused", "budgetLimited", "usageLimited", "complete", "unmet", "cancelled"),
220
224
  tokenBudget: NullableNumber,
221
225
  tokensUsed: Schema.Number,
@@ -234,7 +238,7 @@ var LegacyStateSchema = Schema.Struct({
234
238
  goals: Schema.Record({ key: Schema.String, value: GoalSchema })
235
239
  });
236
240
  var StateSchema = Schema.Struct({
237
- version: Schema.Literal(2),
241
+ version: Schema.Literal(2, 3),
238
242
  goals: Schema.Record({ key: Schema.String, value: GoalSchema }),
239
243
  archives: Schema.optionalWith(Schema.Record({ key: Schema.String, value: Schema.Array(ArchivedGoalSchema) }), {
240
244
  default: () => ({})
@@ -551,6 +555,8 @@ function snapshot(goal) {
551
555
  id: goal.id,
552
556
  sessionID: goal.sessionID,
553
557
  objective: goal.objective,
558
+ ...goal.plan === null ? { plan: null } : {},
559
+ ...goal.planRevision === 0 ? { planRevision: 0 } : {},
554
560
  status: goal.status,
555
561
  tokenBudget: goal.tokenBudget,
556
562
  tokensUsed: goal.tokensUsed,
@@ -678,6 +684,8 @@ function archivedGoal(goal) {
678
684
  id: goal.id,
679
685
  sessionID: goal.sessionID,
680
686
  objective: summarizeText(goal.objective, MAX_ARCHIVED_OBJECTIVE_CHARS),
687
+ ...goal.plan === null ? { plan: null } : {},
688
+ ...goal.planRevision === 0 ? { planRevision: 0 } : {},
681
689
  status: goal.status,
682
690
  tokenBudget: goal.tokenBudget,
683
691
  tokensUsed: goal.tokensUsed,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prevalentware/opencode-goal-plugin",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
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",
package/src/tui.ts CHANGED
@@ -419,10 +419,12 @@ function isGoalSnapshot(value: unknown): value is GoalSnapshot {
419
419
  export async function readPersistedGoal(sessionID: string): Promise<GoalSnapshot | null | undefined> {
420
420
  try {
421
421
  const state: unknown = JSON.parse(await readFile(statePath(), "utf8"))
422
- if (!isRecord(state) || (state.version !== 1 && state.version !== 2) || !isRecord(state.goals)) return undefined
422
+ if (!isRecord(state) || (state.version !== 1 && state.version !== 2 && state.version !== 3) || !isRecord(state.goals)) return undefined
423
423
  const stored = state.goals[sessionID]
424
424
  if (stored === undefined) return null
425
425
  if (!isRecord(stored) || stored.sessionID !== sessionID) return undefined
426
+ if (stored.plan !== undefined && stored.plan !== null) return undefined
427
+ if (stored.planRevision !== undefined && stored.planRevision !== 0) return undefined
426
428
  const sampledAt = currentEpochSeconds()
427
429
  const timeUsedSeconds = typeof stored.timeUsedSeconds === "number" ? stored.timeUsedSeconds : 0
428
430
  const activeSeconds = stored.status === "active" && typeof stored.lastAccountedAt === "number"