@prevalentware/opencode-goal-plugin 0.1.56 → 0.1.58
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 +4 -1
- package/dist/server.js +72 -8
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -326,4 +326,7 @@ The goal sidebar shows the current status, elapsed time, token usage, auto-conti
|
|
|
326
326
|
|
|
327
327
|
Cancelling an active goal from an ACP client is durable when OpenCode emits the user-cancellation events described above. The plugin prevents subsequent goal continuations, including callbacks still preparing a prompt when cancellation arrives. OpenCode owns cancellation of in-flight model requests, tools, subprocesses, and already submitted prompts; the plugin cannot guarantee process termination if the host does not abort them or does not publish a cancellation signal. It does not interpret ordinary idle events, provider-error text, or completed tool calls as user cancellation.
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
Registered `/goal <objective>` and `/resume_goal` commands use the host's public session-wait API to remain pending across automatic continuations until the goal stops, completes, reaches a safety limit, or auto-continuation is disabled. Hosts without that API keep their previous admission behavior. The ACP adapter must also stream each execution owned by the command: an adapter that ends its event consumer after the first execution can still hide later work. Ordinary prompts retain their host-defined turn lifetime.
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
The Promise-based persistence layer uses Effect 3 under the private `effect-goal-state` npm alias. This keeps it separate from modern OpenCode's shared Effect 4 SDK runtime. It remains an external runtime dependency, and no private Effect values cross the plugin API.
|
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
7
7
|
import { mkdir, readFile } from "fs/promises";
|
|
8
8
|
import { dirname as dirname2 } from "path";
|
|
9
|
-
import { Data, Effect, Schema } from "effect";
|
|
9
|
+
import { Data, Effect, Schema } from "effect-goal-state";
|
|
10
10
|
|
|
11
11
|
// src/atomic-write.ts
|
|
12
12
|
import { randomUUID } from "crypto";
|
|
@@ -3744,6 +3744,7 @@ async function setupV2(context) {
|
|
|
3744
3744
|
};
|
|
3745
3745
|
const registrations = [];
|
|
3746
3746
|
let disposed = false;
|
|
3747
|
+
let eventConsumerStopped = false;
|
|
3747
3748
|
function stepKey(sessionID, messageID) {
|
|
3748
3749
|
return `${sessionID}\x00${messageID}`;
|
|
3749
3750
|
}
|
|
@@ -4391,7 +4392,7 @@ async function setupV2(context) {
|
|
|
4391
4392
|
draft.add({
|
|
4392
4393
|
name: command.name,
|
|
4393
4394
|
description: command.description,
|
|
4394
|
-
execute: async (input) => {
|
|
4395
|
+
execute: async (input, execution) => {
|
|
4395
4396
|
markSessionOwnership(input.sessionID, true);
|
|
4396
4397
|
if (command.action === "pause") {
|
|
4397
4398
|
const goal = await getGoal(input.sessionID);
|
|
@@ -4414,12 +4415,60 @@ async function setupV2(context) {
|
|
|
4414
4415
|
...skills ? { skills: skills.map(stripMention) } : {}
|
|
4415
4416
|
};
|
|
4416
4417
|
}
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4418
|
+
const args = input.prompt.text.trim().toLowerCase();
|
|
4419
|
+
const controlOnly = /^(history|status|show|current|pause|stop|cancel|clear|off|reset|none|edit)(?:\s|$)/.test(args);
|
|
4420
|
+
const pursue = command.action === "resume" || command.action === "goal" && args !== "" && !controlOnly;
|
|
4421
|
+
const signal = execution?.signal ? AbortSignal.any([abortController.signal, execution.signal]) : abortController.signal;
|
|
4422
|
+
let admitted = false;
|
|
4423
|
+
const cancel = () => {
|
|
4424
|
+
if (!pursue || disposed)
|
|
4425
|
+
return;
|
|
4426
|
+
goalServices.stopAutonomy?.(input.sessionID);
|
|
4427
|
+
};
|
|
4428
|
+
execution?.signal?.addEventListener("abort", cancel, { once: true });
|
|
4429
|
+
try {
|
|
4430
|
+
if (execution?.signal?.aborted) {
|
|
4431
|
+
cancel();
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
4434
|
+
await context.session.prompt({
|
|
4435
|
+
...forwardedPrompt,
|
|
4436
|
+
sessionID: input.sessionID,
|
|
4437
|
+
text: command.template.replaceAll("$ARGUMENTS", () => escapeXmlText2(input.prompt.text.trim())),
|
|
4438
|
+
delivery: input.delivery
|
|
4439
|
+
});
|
|
4440
|
+
admitted = true;
|
|
4441
|
+
if (pursue && typeof context.session.wait === "function") {
|
|
4442
|
+
let pursuedGoalID;
|
|
4443
|
+
do {
|
|
4444
|
+
await context.session.wait({ sessionID: input.sessionID }, { signal });
|
|
4445
|
+
if (eventConsumerStopped)
|
|
4446
|
+
throw new Error("goal event stream stopped");
|
|
4447
|
+
const goal = await getGoal(input.sessionID);
|
|
4448
|
+
if (signal.aborted || disposed || stoppedExecutions.has(input.sessionID) || !autoContinue || goal?.status !== "active")
|
|
4449
|
+
break;
|
|
4450
|
+
pursuedGoalID ??= goal.id;
|
|
4451
|
+
if (goal.id !== pursuedGoalID)
|
|
4452
|
+
break;
|
|
4453
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
4454
|
+
} while (!disposed && !signal.aborted);
|
|
4455
|
+
}
|
|
4456
|
+
} catch (error) {
|
|
4457
|
+
if (!admitted && !disposed && !execution?.signal?.aborted)
|
|
4458
|
+
throw error;
|
|
4459
|
+
if (!disposed && !execution?.signal?.aborted) {
|
|
4460
|
+
goalServices.stopAutonomy?.(input.sessionID);
|
|
4461
|
+
v2ErrorLog("Goal command wait failed; autonomous continuation stopped", error);
|
|
4462
|
+
try {
|
|
4463
|
+
if ((await getGoal(input.sessionID))?.status === "active")
|
|
4464
|
+
await setGoalStatus(input.sessionID, "paused");
|
|
4465
|
+
} catch (stateError) {
|
|
4466
|
+
v2ErrorLog("Failed to pause goal after command failure", stateError);
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
} finally {
|
|
4470
|
+
execution?.signal?.removeEventListener("abort", cancel);
|
|
4471
|
+
}
|
|
4423
4472
|
}
|
|
4424
4473
|
});
|
|
4425
4474
|
}
|
|
@@ -4451,6 +4500,18 @@ async function setupV2(context) {
|
|
|
4451
4500
|
}
|
|
4452
4501
|
}));
|
|
4453
4502
|
}
|
|
4503
|
+
try {
|
|
4504
|
+
const hookInterrupt = context.session.hook;
|
|
4505
|
+
registrations.push(await hookInterrupt("interrupt", async ({ sessionID }) => {
|
|
4506
|
+
markSessionOwnership(sessionID, true);
|
|
4507
|
+
goalServices.stopAutonomy?.(sessionID);
|
|
4508
|
+
try {
|
|
4509
|
+
await cancelActiveGoal(sessionID);
|
|
4510
|
+
} catch (error) {
|
|
4511
|
+
v2ErrorLog("Failed to persist explicit session cancellation", error);
|
|
4512
|
+
}
|
|
4513
|
+
}));
|
|
4514
|
+
} catch {}
|
|
4454
4515
|
registrations.push(await context.tool.transform((draft) => {
|
|
4455
4516
|
for (const tool of goalToolsV2(goalServices))
|
|
4456
4517
|
draft.add(tool);
|
|
@@ -4552,6 +4613,9 @@ async function setupV2(context) {
|
|
|
4552
4613
|
} catch (error) {
|
|
4553
4614
|
if (!abortController.signal.aborted)
|
|
4554
4615
|
v2ErrorLog("V2 event consumer stopped", error);
|
|
4616
|
+
} finally {
|
|
4617
|
+
if (!disposed && !abortController.signal.aborted)
|
|
4618
|
+
eventConsumerStopped = true;
|
|
4555
4619
|
}
|
|
4556
4620
|
})();
|
|
4557
4621
|
return async () => {
|
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.58",
|
|
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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
48
|
"clean": "rm -rf dist",
|
|
49
|
-
"build": "bun run clean && bun build ./src/server.ts --outdir ./dist --target bun --external @opencode-ai/plugin --external effect --external zod",
|
|
49
|
+
"build": "bun run clean && bun build ./src/server.ts --outdir ./dist --target bun --external @opencode-ai/plugin --external effect-goal-state --external zod",
|
|
50
50
|
"ci:version": "bun scripts/resolve-ci-version.ts",
|
|
51
51
|
"lint": "eslint .",
|
|
52
52
|
"pack:dry-run": "npm pack --dry-run",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@opencode-ai/plugin": "^1.17.1",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
61
|
+
"zod": "^4.1.8",
|
|
62
|
+
"effect-goal-state": "npm:effect@^3.21.2"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@opentui/solid": ">=0.5.8",
|