@glrs-dev/harness-plugin-opencode 2.3.0 → 2.4.0

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#72](https://github.com/iceglober/glrs/pull/72) [`0aa23d4`](https://github.com/iceglober/glrs/commit/0aa23d432d92f9349dc3f3c37994e336dc19d197) Thanks [@iceglober](https://github.com/iceglober)! - Wave 2 — autopilot execution reliability and resume.
8
+
9
+ - **Transient error retry.** `sendAndWait` errors classified as transient (network blips, 429, 5xx, throttling) trigger up to 3 attempts with exponential backoff (1s → 2s → 4s, capped at 30s). Permanent errors (400, validation) fail immediately.
10
+ - **Resume from checkpoint.** `--resume` reads `.agent/autopilot-checkpoint.json` and skips already-completed phases (when the checkpoint's `planPath` matches the current `--plan`). The checkpoint is written atomically after each phase and deleted on successful run completion.
11
+ - **Adaptive stall timeout.** The per-iteration stall timeout now adapts to the model tier: deep=30m, mid=15m, mid-execute/autopilot-execute=10m, fast=5m. Override with `--stall-timeout <ms>`.
12
+ - **Graceful shutdown.** SIGINT/SIGTERM triggers a graceful shutdown: aborts the current iteration, commits any working-tree changes as `[WIP] autopilot interrupted`, writes a checkpoint, then exits. A second signal force-exits with code 130.
13
+ - **Phase-level git safety.** In `--fast` mode, a failed phase soft-resets to the pre-phase HEAD so the user gets a clean state with all changes preserved in staging. Interactive mode leaves the work in place for manual review.
14
+ - **Credential refresh detection.** API errors classified as `credential-expired` (AWS STS, Azure token) write a checkpoint and exit with code 2 + a clear message: "Run `gs-assume` and then `glrs oc autopilot --resume`."
15
+ - **Per-phase iteration budget.** `--max-iterations-per-phase` (default: deep=5, mid-execute/fast=10) caps a single phase's iteration count. A phase that hits its budget without completing logs a warning, writes a checkpoint, and the run continues to the next phase rather than terminating.
16
+
3
17
  ## 2.3.0
4
18
 
5
19
  ### Minor Changes
@@ -104,9 +104,16 @@ Multi-file plan template:
104
104
 
105
105
  ## Acceptance criteria
106
106
 
107
+ Each item in the plan-state fence **must** include a `files:` field listing every file the item touches. For each file entry, provide the path (with `(NEW)` if the file does not yet exist) and a one-sentence `Change:` description. This gives the executor file-level specificity without requiring codebase exploration.
108
+
107
109
  \`\`\`plan-state
108
110
  - [ ] id: a1
109
111
  intent: <item>
112
+ files:
113
+ - <path/to/file> (NEW)
114
+ Change: <one sentence describing what to create or modify>
115
+ - <path/to/other-file>
116
+ Change: <one sentence>
110
117
  tests:
111
118
  - <path>::"<name>"
112
119
  verify: <command>
@@ -0,0 +1,66 @@
1
+ // src/cli/plugin-check.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import { select, checkbox, confirm } from "@inquirer/prompts";
6
+ var PLUGIN_NAME = "@glrs-dev/harness-plugin-opencode";
7
+ function getOpencodeConfigPath() {
8
+ const configHome = process.env["XDG_CONFIG_HOME"] ?? path.join(os.homedir(), ".config");
9
+ return path.join(configHome, "opencode", "opencode.json");
10
+ }
11
+ function isPluginInstalled() {
12
+ const configPath = getOpencodeConfigPath();
13
+ if (!fs.existsSync(configPath)) return false;
14
+ try {
15
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
16
+ const plugins = Array.isArray(config.plugin) ? config.plugin : [];
17
+ return plugins.some((p) => {
18
+ const name = typeof p === "string" ? p : Array.isArray(p) ? p[0] : null;
19
+ return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
20
+ });
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ async function promptYesNo(question) {
26
+ if (!process.stdin.isTTY) return false;
27
+ return confirm({ message: question, default: false });
28
+ }
29
+ async function promptChoice(question, choices, defaultIndex = 0) {
30
+ if (!process.stdin.isTTY) return defaultIndex;
31
+ const answer = await select({
32
+ message: question,
33
+ choices: choices.map((label, i) => ({
34
+ name: label,
35
+ value: i
36
+ })),
37
+ default: defaultIndex
38
+ });
39
+ return answer;
40
+ }
41
+ async function promptMulti(question, choices) {
42
+ if (!process.stdin.isTTY) {
43
+ const defaults = /* @__PURE__ */ new Set();
44
+ choices.forEach((c, i) => {
45
+ if (c.defaultOn) defaults.add(i);
46
+ });
47
+ return defaults;
48
+ }
49
+ const answers = await checkbox({
50
+ message: question,
51
+ choices: choices.map((c, i) => ({
52
+ name: c.label,
53
+ value: i,
54
+ checked: c.defaultOn
55
+ }))
56
+ });
57
+ return new Set(answers);
58
+ }
59
+
60
+ export {
61
+ getOpencodeConfigPath,
62
+ isPluginInstalled,
63
+ promptYesNo,
64
+ promptChoice,
65
+ promptMulti
66
+ };