@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.
- package/dist/server.js +82 -20
- 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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
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": {
|