opencode-goal-plugin 0.6.0 → 0.6.1
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/CHANGELOG.md +13 -1
- package/README.md +17 -14
- package/index.d.ts +4 -1
- package/package.json +13 -4
- package/src/goal-plugin.js +608 -197
- package/src/native-agent-config.js +5 -1
- package/src/opencode-session-api.js +11 -1
- package/src/persistence-lease.js +14 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const GOAL_AGENT_PROMPT = `Execute explicit goals persistently. Use goal tools to track state and checkpoints. Make concrete progress; claim completion only with verification evidence. Report only genuine blockers.`
|
|
2
2
|
|
|
3
|
-
const VERIFIER_AGENT_PROMPT = `Independently verify the claim against the goal, constraints, evidence, and workspace. Use
|
|
3
|
+
const VERIFIER_AGENT_PROMPT = `Independently verify the claim against the goal, constraints, evidence, and workspace. Use only the read, glob, and grep tools; never edit, execute commands, call other tools, or mutate goal state. Approve only when proven; otherwise give one actionable reason.`
|
|
4
4
|
|
|
5
5
|
export function applyNativeGoalConfig(config, options = {}) {
|
|
6
6
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
@@ -40,6 +40,10 @@ export function applyNativeGoalConfig(config, options = {}) {
|
|
|
40
40
|
hidden: true,
|
|
41
41
|
prompt: VERIFIER_AGENT_PROMPT,
|
|
42
42
|
permission: {
|
|
43
|
+
"*": "deny",
|
|
44
|
+
read: "allow",
|
|
45
|
+
glob: "allow",
|
|
46
|
+
grep: "allow",
|
|
43
47
|
edit: "deny",
|
|
44
48
|
bash: "deny",
|
|
45
49
|
},
|
|
@@ -5,6 +5,11 @@ const SHAPE_ERROR_PATTERNS = [
|
|
|
5
5
|
/(?:validation|schema|invalid input|invalid argument)/i,
|
|
6
6
|
]
|
|
7
7
|
|
|
8
|
+
// Only read-only operations may be retried with another argument shape. A
|
|
9
|
+
// TypeError can be raised after a mutating SDK call has already reached the
|
|
10
|
+
// host, so replaying create/prompt/update/delete/abort could duplicate side effects.
|
|
11
|
+
const REPLAY_SAFE_OPERATIONS = new Set(["messages", "get"])
|
|
12
|
+
|
|
8
13
|
function isArgumentShapeError(error) {
|
|
9
14
|
if (!(error instanceof TypeError)) return false
|
|
10
15
|
const message = String(error.message || "")
|
|
@@ -46,7 +51,9 @@ export function createOpenCodeSessionApi(client, options = {}) {
|
|
|
46
51
|
shapes.set(operation, firstShape)
|
|
47
52
|
return unwrapData(response)
|
|
48
53
|
} catch (error) {
|
|
49
|
-
if (knownShape || !isArgumentShapeError(error))
|
|
54
|
+
if (knownShape || !REPLAY_SAFE_OPERATIONS.has(operation) || !isArgumentShapeError(error)) {
|
|
55
|
+
throw error
|
|
56
|
+
}
|
|
50
57
|
const fallbackShape = firstShape === "flat" ? "legacy" : "flat"
|
|
51
58
|
const fallbackInput = fallbackShape === "flat" ? flatInput : legacyInput
|
|
52
59
|
const response = await method.call(client.session, fallbackInput)
|
|
@@ -91,6 +98,9 @@ export function createOpenCodeSessionApi(client, options = {}) {
|
|
|
91
98
|
get(sessionID) {
|
|
92
99
|
return invoke("get", { sessionID }, { path: { id: sessionID } })
|
|
93
100
|
},
|
|
101
|
+
delete(sessionID) {
|
|
102
|
+
return invoke("delete", { sessionID }, { path: { id: sessionID } })
|
|
103
|
+
},
|
|
94
104
|
abort(sessionID) {
|
|
95
105
|
return invoke("abort", { sessionID }, { path: { id: sessionID } })
|
|
96
106
|
},
|
package/src/persistence-lease.js
CHANGED
|
@@ -27,7 +27,10 @@ async function readOwner(lockPath) {
|
|
|
27
27
|
* deliberately rejects a second writer instead of allowing stale full-state
|
|
28
28
|
* snapshots to overwrite each other.
|
|
29
29
|
*/
|
|
30
|
-
export async function acquirePersistenceLease(
|
|
30
|
+
export async function acquirePersistenceLease(
|
|
31
|
+
stateFilePath,
|
|
32
|
+
{ malformedGraceMs = 30_000, now = () => Date.now() } = {},
|
|
33
|
+
) {
|
|
31
34
|
const lockPath = `${stateFilePath}.lock`
|
|
32
35
|
await fs.mkdir(dirname(stateFilePath), { recursive: true, mode: 0o700 })
|
|
33
36
|
const owner = {
|
|
@@ -58,7 +61,16 @@ export async function acquirePersistenceLease(stateFilePath) {
|
|
|
58
61
|
}
|
|
59
62
|
const existing = await readOwner(lockPath)
|
|
60
63
|
const sameHost = existing?.hostname === owner.hostname
|
|
61
|
-
|
|
64
|
+
let reclaimableMalformed = false
|
|
65
|
+
if (!existing) {
|
|
66
|
+
try {
|
|
67
|
+
const info = await fs.lstat(lockPath)
|
|
68
|
+
reclaimableMalformed = now() - info.mtimeMs >= malformedGraceMs
|
|
69
|
+
} catch (statError) {
|
|
70
|
+
if (statError?.code === "ENOENT") continue
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if ((sameHost && processIsAlive(existing?.pid) === false) || reclaimableMalformed) {
|
|
62
74
|
const stalePath = `${lockPath}.stale.${randomUUID()}`
|
|
63
75
|
try {
|
|
64
76
|
await fs.rename(lockPath, stalePath)
|