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.
@@ -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 read-only checks; never edit or mutate goal state. Approve only when proven; otherwise give one actionable reason.`
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)) throw 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
  },
@@ -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(stateFilePath) {
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
- if (sameHost && processIsAlive(existing?.pid) === false) {
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)