@prevalentware/opencode-goal-plugin 0.1.10 → 0.1.12

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.
Files changed (2) hide show
  1. package/dist/server.js +82 -20
  2. package/package.json +3 -2
package/dist/server.js CHANGED
@@ -6,6 +6,38 @@ import { z } from "zod";
6
6
  import { homedir } from "os";
7
7
  import { dirname, join } from "path";
8
8
  import { mkdir, readFile, rename, writeFile } from "fs/promises";
9
+ import { Data, Effect, Schema } from "effect";
10
+
11
+ class StateReadError extends Data.TaggedError("StateReadError") {
12
+ }
13
+
14
+ class StateDecodeError extends Data.TaggedError("StateDecodeError") {
15
+ }
16
+
17
+ class StateWriteError extends Data.TaggedError("StateWriteError") {
18
+ }
19
+ var NullableString = Schema.NullOr(Schema.String);
20
+ var NullableNumber = Schema.NullOr(Schema.Number);
21
+ var GoalSchema = Schema.Struct({
22
+ sessionID: Schema.String,
23
+ objective: Schema.String,
24
+ status: Schema.Literal("active", "paused", "budgetLimited", "complete", "unmet"),
25
+ tokenBudget: NullableNumber,
26
+ tokensUsed: Schema.Number,
27
+ timeUsedSeconds: Schema.Number,
28
+ createdAt: Schema.Number,
29
+ updatedAt: Schema.Number,
30
+ completionEvidence: Schema.optionalWith(NullableString, { default: () => null }),
31
+ blocker: Schema.optionalWith(NullableString, { default: () => null }),
32
+ closedAt: Schema.optionalWith(NullableNumber, { default: () => null }),
33
+ lastAccountedAt: NullableNumber,
34
+ autoTurns: Schema.Number,
35
+ lastContinuationAt: NullableNumber
36
+ });
37
+ var StateSchema = Schema.Struct({
38
+ version: Schema.Literal(1),
39
+ goals: Schema.Record({ key: Schema.String, value: GoalSchema })
40
+ });
9
41
  function defaultStateFile() {
10
42
  const dataHome = process.env.XDG_DATA_HOME || (process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : join(homedir(), ".local", "share"));
11
43
  return join(dataHome, "opencode-goal-plugin", "goals.json");
@@ -19,30 +51,60 @@ function nowSeconds() {
19
51
  function emptyState() {
20
52
  return { version: 1, goals: {} };
21
53
  }
54
+ function isMissingStateFile(error) {
55
+ return typeof error === "object" && error !== null && error.code === "ENOENT";
56
+ }
57
+ function mutableState(state) {
58
+ return JSON.parse(JSON.stringify(state));
59
+ }
60
+ function decodeState(value) {
61
+ return Schema.decodeUnknown(StateSchema)(value).pipe(Effect.map(mutableState), Effect.mapError((cause) => new StateDecodeError({ cause })));
62
+ }
63
+ function readStateEffect() {
64
+ return Effect.tryPromise({
65
+ try: () => readFile(statePath(), "utf8"),
66
+ catch: (cause) => new StateReadError({ cause })
67
+ }).pipe(Effect.flatMap((raw) => Effect.try({
68
+ try: () => JSON.parse(raw),
69
+ catch: (cause) => new StateDecodeError({ cause })
70
+ })), Effect.flatMap(decodeState), Effect.catchAll((error) => error._tag === "StateReadError" && isMissingStateFile(error.cause) ? Effect.succeed(emptyState()) : Effect.fail(error)));
71
+ }
72
+ function writeStateEffect(state) {
73
+ return Effect.tryPromise({
74
+ try: async () => {
75
+ const file = statePath();
76
+ await mkdir(dirname(file), { recursive: true });
77
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
78
+ await writeFile(tmp, JSON.stringify(state, null, 2) + `
79
+ `);
80
+ await rename(tmp, file);
81
+ },
82
+ catch: (cause) => new StateWriteError({ cause })
83
+ });
84
+ }
22
85
  async function readState() {
23
- try {
24
- const raw = await readFile(statePath(), "utf8");
25
- const parsed = JSON.parse(raw);
26
- return parsed && parsed.version === 1 && parsed.goals ? parsed : emptyState();
27
- } catch (error) {
28
- if (error.code === "ENOENT")
29
- return emptyState();
30
- throw error;
31
- }
86
+ return Effect.runPromise(readStateEffect());
32
87
  }
33
- async function writeState(state) {
34
- const file = statePath();
35
- await mkdir(dirname(file), { recursive: true });
36
- const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
37
- await writeFile(tmp, JSON.stringify(state, null, 2) + `
38
- `);
39
- await rename(tmp, file);
88
+ var mutationQueue = Promise.resolve();
89
+ function enqueueMutation(operation) {
90
+ const current = mutationQueue.then(operation, operation);
91
+ mutationQueue = current.then(() => {
92
+ return;
93
+ }, () => {
94
+ return;
95
+ });
96
+ return current;
40
97
  }
41
98
  async function mutate(fn) {
42
- const state = await readState();
43
- const result = await fn(state);
44
- await writeState(state);
45
- return result;
99
+ return enqueueMutation(() => Effect.runPromise(Effect.gen(function* () {
100
+ const state = yield* readStateEffect();
101
+ const result = yield* Effect.tryPromise({
102
+ try: () => Promise.resolve(fn(state)),
103
+ catch: (cause) => cause instanceof Error ? cause : new Error(String(cause))
104
+ });
105
+ yield* writeStateEffect(state);
106
+ return result;
107
+ })));
46
108
  }
47
109
  function validateObjective(objective) {
48
110
  const value = objective.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prevalentware/opencode-goal-plugin",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Codex-style long-running goal mode for OpenCode.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -36,7 +36,7 @@
36
36
  ],
37
37
  "scripts": {
38
38
  "clean": "rm -rf dist",
39
- "build": "bun run clean && bun build ./src/server.ts --outdir ./dist --target bun --external @opencode-ai/plugin --external zod",
39
+ "build": "bun run clean && bun build ./src/server.ts --outdir ./dist --target bun --external @opencode-ai/plugin --external effect --external zod",
40
40
  "ci:version": "bun scripts/resolve-ci-version.ts",
41
41
  "lint": "eslint .",
42
42
  "pack:dry-run": "npm pack --dry-run",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@opencode-ai/plugin": "^1.14.39",
49
+ "effect": "^3.21.2",
49
50
  "zod": "^4.1.8"
50
51
  },
51
52
  "devDependencies": {