opencode-workflow-guard 1.9.1 → 1.9.3
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/docs/installation.md +1 -1
- package/docs/policies.md +1 -1
- package/docs/superpowers/plans/2026-08-28-title-settle-default.md +122 -0
- package/docs/superpowers/specs/2026-08-28-title-settle-default-design.md +25 -0
- package/package.json +1 -1
- package/src/workflow-guard-ui.ts +14 -14
- package/src/workflow-guard.ts +1 -1
- package/test/test.mts +19 -17
package/docs/installation.md
CHANGED
|
@@ -100,7 +100,7 @@ When the root session reaches idle, the checkpoint records the resulting workspa
|
|
|
100
100
|
|
|
101
101
|
With the optional TUI companion installed, open the command palette and choose **Workflow Guard: Project Options** (or run `/guard-options`) to toggle recovery checkpoints, project memory, learner mode, Ralph mode, or the title-settle workaround for the current project. The command writes the existing Workflow Guard project config location, defaulting to `.opencode/workflow-guard.json`; restart OpenCode after changing a setting so the server plugin can expose the matching tools.
|
|
102
102
|
|
|
103
|
-
The title-settle workaround is
|
|
103
|
+
The title-settle workaround is on by default because Workflow Guard's automatic continuation can race OpenCode's asynchronous native title generation. Workflow Guard briefly waits for native title generation before sending an automatic continuation for unfinished root-session work; set `titleSettleWorkaround: false` to opt out. The wait is bounded, applies only when that session owns unfinished todos, and never changes the title or provider/model parameters itself.
|
|
104
104
|
|
|
105
105
|
Ralph mode is also off by default, including under the `autonomous` profile. Enable it explicitly with `ralphMode: true`; `ralphMaxIterations` sets its positive integer continuation budget from 1 through 100 (default 10; invalid values use the default). Ralph only continues a root session while that session already owns unfinished native todos. It does not create, prioritize, select, sequence, or delegate work, and every resumed run remains subject to the normal guardrails. Genuine user input stops an active Ralph run instead of resetting its budget. Reaching the budget with unfinished work reports `budget_exhausted`; completion is reported only when owned todos are actually finished. A `blocked` outcome is reserved for a demonstrable guard state and is never inferred merely from repeated idle events. `guard_status` exposes the effective Ralph configuration and current calling-session outcome.
|
|
106
106
|
|
package/docs/policies.md
CHANGED
|
@@ -83,7 +83,7 @@ It is a policy and enforcement layer, not an agent harness. Policies may constra
|
|
|
83
83
|
- Verification (`WORKFLOW_GUARD_VERIFY` env, project `verifyCommand`, or auto-detected test/typecheck runner across ecosystems: `package.json` (`npm test`, `npm run typecheck`, `npm run check`, `npm run build`), `Cargo.toml` (`cargo test`), `go.mod` (`go test ./...`), `pytest.ini`/`pyproject.toml` (`pytest`), or `deno.json` (`deno test`)) executes in an isolated environment with scrubbed credentials, output caps, token-efficient stdout/stderr snipping, and strict timeout protection. Environment configuration takes precedence over project configuration.
|
|
84
84
|
- Project `postEditValidators` can run faster targeted checks after a direct file edit actually changes the target. Each entry accepts a workspace-relative glob `pattern` (`*`, `**`, and `?` wildcards; brace expansion and character classes are not supported), a non-empty `command`, and an optional positive `timeoutMs`; invalid entries are reported as advisory feedback rather than silently ignored. Matching commands run from the workspace root with the same scrubbed verification environment. Validator failures and timeouts are appended to the edit result as advisory feedback and do not replace the blocking whole-project finalization verification. Multi-file `apply_patch` calls validate every changed target.
|
|
85
85
|
- With project `recoveryCheckpoints: true`, or the `autonomous` operation profile unless explicitly overridden, genuine root-session user runs capture a private Git checkpoint before agent work starts. Checkpoints preserve tracked and untracked state without using the user's stash list; subagents and generated continuation prompts do not replace them. At `session.idle`, the run's resulting workspace fingerprint is recorded before automatic continuation. `guard_recovery_restore` is provenance-bound to the same root session and refuses restoration after any intervening workspace change. Operation profiles only set optional workflow defaults and cannot weaken core safety policies.
|
|
86
|
-
- Automatic continuation remains synthetic.
|
|
86
|
+
- Automatic continuation remains synthetic. By default, an eligible root session with unfinished owned todos briefly waits for OpenCode's native asynchronous title generation before that continuation is sent; set `titleSettleWorkaround: false` to opt out. The bounded wait never rewrites titles or provider/model parameters.
|
|
87
87
|
- **Git State & Snapshot Binding:** Verification evidence records the current git commit hash (`git rev-parse HEAD`) and working tree status. If unverified edits or index modifications occur after verification, fresh verification is enforced.
|
|
88
88
|
- **Durable Disk Cache:** Passing verification results are cached to `~/.local/state/opencode/workflow-guard/last-verify.json`, allowing session restarts and multi-agent handoffs to retain valid verification evidence without redundant test runs. Durable evidence is **workspace-bound**: a cached result is only accepted for the workspace that produced it, so a passing run in one project can never satisfy finalization in another.
|
|
89
89
|
- If verification fails, finalization is blocked with a structured summary of error markers, stack traces, and failure output.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Title Settle Default Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make Workflow Guard's title-settle behavior enabled by default while preserving `titleSettleWorkaround: false` as an explicit opt-out.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Treat the missing setting consistently as enabled at both consumers: the continuation server hook and TUI project-option reader. Keep the existing bounded title-settle implementation unchanged; only its effective default changes.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, OpenCode TUI plugin API, Node test harness, jsonc-parser.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Lock Down Default And Opt-Out Semantics
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `test/test.mts:1140-1155,2835-2860`
|
|
17
|
+
|
|
18
|
+
- [ ] **Step 1: Change the TUI regression to expect title settling on without configuration**
|
|
19
|
+
|
|
20
|
+
Before selecting the title-settle option, assert the missing setting reads as enabled. Then select its option callback and assert the persisted effective value becomes false:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
24
|
+
check("title settle workaround project option defaults on", readProjectOption(tuiCommandOptionsDir, "titleSettleWorkaround") === true);
|
|
25
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "titleSettleWorkaround")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
26
|
+
check("tui project-options command can disable title settle workaround", readProjectOption(tuiCommandOptionsDir, "titleSettleWorkaround") === false);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- [ ] **Step 2: Change continuation regressions to cover default-on and explicit opt-out**
|
|
30
|
+
|
|
31
|
+
Use the existing title-settle continuation fixture around line 2844. Remove the requirement to write `{ titleSettleWorkaround: true }` for the positive case, and add a sibling case/fixture with `{ titleSettleWorkaround: false }` proving continuation does not wait for title settling when explicitly disabled. Preserve the existing assertions that title generation remains native and the wait remains bounded.
|
|
32
|
+
|
|
33
|
+
- [ ] **Step 3: Run the unit harness to verify the new expectations fail**
|
|
34
|
+
|
|
35
|
+
Run: `npm test`
|
|
36
|
+
|
|
37
|
+
Expected: the new default-on assertions fail against current production code while unrelated checks remain green.
|
|
38
|
+
|
|
39
|
+
### Task 2: Implement Enabled-Unless-False Semantics
|
|
40
|
+
|
|
41
|
+
**Files:**
|
|
42
|
+
- Modify: `src/workflow-guard-ui.ts:36-43`
|
|
43
|
+
- Modify: `src/workflow-guard.ts:672`
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 1: Make the TUI reader default title settling to enabled**
|
|
46
|
+
|
|
47
|
+
Update `readProjectOption` so both project memory and title settling are enabled unless explicitly false, while the other opt-in settings remain enabled only by explicit true:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const enabledByDefault = option === "projectMemory" || option === "titleSettleWorkaround";
|
|
51
|
+
if (!existsSync(path)) return enabledByDefault;
|
|
52
|
+
// parse existing config as before
|
|
53
|
+
return enabledByDefault ? config?.[option] !== false : config?.[option] === true;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- [ ] **Step 2: Make continuation use the same effective default**
|
|
57
|
+
|
|
58
|
+
Change the title-settle gate in `src/workflow-guard.ts` from:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround === true;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
to:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround !== false;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- [ ] **Step 3: Run the unit harness**
|
|
71
|
+
|
|
72
|
+
Run: `npm test`
|
|
73
|
+
|
|
74
|
+
Expected: all checks pass, including default-enabled and explicit-false regressions.
|
|
75
|
+
|
|
76
|
+
### Task 3: Document The New Default
|
|
77
|
+
|
|
78
|
+
**Files:**
|
|
79
|
+
- Modify: `docs/installation.md:103`
|
|
80
|
+
- Modify: `docs/policies.md:86`
|
|
81
|
+
- Modify: `.changeset/fix-project-option-toggles.md`
|
|
82
|
+
|
|
83
|
+
- [ ] **Step 1: Update installation guidance**
|
|
84
|
+
|
|
85
|
+
Replace the statement that title settling is off by default with text stating that it is on by default because Workflow Guard's automatic continuation can race OpenCode title generation, and that projects may opt out with `titleSettleWorkaround: false`.
|
|
86
|
+
|
|
87
|
+
- [ ] **Step 2: Update policy documentation**
|
|
88
|
+
|
|
89
|
+
Describe title settling as the default continuation behavior rather than opt-in behavior. Retain the guarantees that the wait is bounded and does not rewrite titles or provider/model parameters.
|
|
90
|
+
|
|
91
|
+
- [ ] **Step 3: Expand the existing patch changeset**
|
|
92
|
+
|
|
93
|
+
Make `.changeset/fix-project-option-toggles.md` mention both user-visible changes: functioning TUI toggles and title settling enabled by default with explicit opt-out.
|
|
94
|
+
|
|
95
|
+
### Task 4: Verify The Final Tree
|
|
96
|
+
|
|
97
|
+
**Files:**
|
|
98
|
+
- Verify only
|
|
99
|
+
|
|
100
|
+
- [ ] **Step 1: Run type checking**
|
|
101
|
+
|
|
102
|
+
Run: `npm run typecheck`
|
|
103
|
+
|
|
104
|
+
Expected: exit 0 with no TypeScript errors.
|
|
105
|
+
|
|
106
|
+
- [ ] **Step 2: Run the full unit/adversarial harness**
|
|
107
|
+
|
|
108
|
+
Run: `npm test`
|
|
109
|
+
|
|
110
|
+
Expected: all checks pass with 0 failures.
|
|
111
|
+
|
|
112
|
+
- [ ] **Step 3: Run packaged installation verification**
|
|
113
|
+
|
|
114
|
+
Run: `npm run test:install`
|
|
115
|
+
|
|
116
|
+
Expected: all non-model-driven installation/runtime checks pass with 0 failures.
|
|
117
|
+
|
|
118
|
+
- [ ] **Step 4: Check the diff**
|
|
119
|
+
|
|
120
|
+
Run: `git diff --check`
|
|
121
|
+
|
|
122
|
+
Expected: exit 0 with no whitespace errors.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Title Settle Default Design
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Workflow Guard can automatically continue a root session that still owns unfinished todos. That synthetic continuation can race OpenCode's asynchronous native session-title generation. The existing `titleSettleWorkaround` behavior avoids the race by briefly waiting for native title generation, but it currently requires explicit opt-in even though Workflow Guard's continuation behavior creates the need for the workaround.
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
Enable the title-settle behavior by default. A missing `titleSettleWorkaround` setting means enabled; `titleSettleWorkaround: false` remains an explicit project-level opt-out.
|
|
10
|
+
|
|
11
|
+
The server and TUI must expose the same effective semantics. The project-options menu therefore renders Title settle workaround as On when the setting is absent, and selecting it persists `false`. Selecting it again persists `true`.
|
|
12
|
+
|
|
13
|
+
No configuration migration is required. Existing projects that do not mention the setting acquire the safer default automatically, while projects already configured with `false` retain their behavior.
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
- Change effective server behavior from `=== true` to enabled unless explicitly false.
|
|
18
|
+
- Change the TUI project-option reader to report the same enabled-by-default value.
|
|
19
|
+
- Preserve all existing bounded-wait, ownership, title, provider, and model behavior.
|
|
20
|
+
- Update installation and policy documentation to describe the default and opt-out.
|
|
21
|
+
- Update regression coverage for default-enabled behavior and explicit-false opt-out.
|
|
22
|
+
|
|
23
|
+
## Verification
|
|
24
|
+
|
|
25
|
+
Regression tests must prove that an unconfigured project uses title settling, explicit `false` bypasses title settling, and the TUI reports/toggles the same effective state. The repository typecheck, unit harness, and packaged-install test must remain green.
|
package/package.json
CHANGED
package/src/workflow-guard-ui.ts
CHANGED
|
@@ -35,11 +35,12 @@ type ProjectToggle = "recoveryCheckpoints" | "projectMemory" | "learning" | "tit
|
|
|
35
35
|
|
|
36
36
|
export function readProjectOption(root: string, option: ProjectToggle): boolean {
|
|
37
37
|
const path = projectConfigPath(root);
|
|
38
|
-
|
|
38
|
+
const enabledByDefault = option === "projectMemory" || option === "titleSettleWorkaround";
|
|
39
|
+
if (!existsSync(path)) return enabledByDefault;
|
|
39
40
|
const errors: ParseError[] = [];
|
|
40
41
|
const config = parse(readFileSync(path, "utf8"), errors, { allowTrailingComma: true });
|
|
41
42
|
if (errors.length > 0) throw new Error(`Invalid Workflow Guard project config: ${path}`);
|
|
42
|
-
return
|
|
43
|
+
return enabledByDefault ? config?.[option] !== false : config?.[option] === true;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function writeProjectOption(root: string, option: ProjectToggle, enabled: boolean): string {
|
|
@@ -89,23 +90,22 @@ export const WorkflowGuardTui: TuiPlugin = async (api) => {
|
|
|
89
90
|
const learning = readProjectOption(root, "learning");
|
|
90
91
|
const titleSettle = readProjectOption(root, "titleSettleWorkaround");
|
|
91
92
|
const ralphMode = readProjectOption(root, "ralphMode");
|
|
93
|
+
const toggle = (key: ProjectToggle) => {
|
|
94
|
+
const enabled = !readProjectOption(root, key);
|
|
95
|
+
const path = writeProjectOption(root, key, enabled);
|
|
96
|
+
api.ui.dialog.clear();
|
|
97
|
+
api.ui.toast({ variant: "success", title: "Workflow Guard", message: `Saved ${key} ${enabled ? "on" : "off"} in ${path}. Restart OpenCode to apply.` });
|
|
98
|
+
};
|
|
92
99
|
api.ui.dialog.replace(() => api.ui.DialogSelect({
|
|
93
100
|
title: "Workflow Guard Project Options",
|
|
94
101
|
current: undefined,
|
|
95
102
|
options: [
|
|
96
|
-
{ title: `Recovery checkpoints: ${recovery ? "On" : "Off"}`, value: "recoveryCheckpoints", description: "Toggle durable pre-run Git checkpoints" },
|
|
97
|
-
{ title: `Project memory: ${memory ? "On" : "Off"}`, value: "projectMemory", description: "Toggle durable local project memory" },
|
|
98
|
-
{ title: `Learner mode: ${learning ? "On" : "Off"}`, value: "learning", description: "Toggle evidence-based learning tools" },
|
|
99
|
-
{ title: `Title settle workaround: ${titleSettle ? "On" : "Off"}`, value: "titleSettleWorkaround", description: "Delay automatic continuation while OpenCode generates a session title" },
|
|
100
|
-
{ title: `Ralph mode: ${ralphMode ? "On" : "Off"}`, value: "ralphMode", description: "Opt into bounded autonomous continuation of already-owned todos" },
|
|
103
|
+
{ title: `Recovery checkpoints: ${recovery ? "On" : "Off"}`, value: "recoveryCheckpoints", description: "Toggle durable pre-run Git checkpoints", onSelect: () => toggle("recoveryCheckpoints") },
|
|
104
|
+
{ title: `Project memory: ${memory ? "On" : "Off"}`, value: "projectMemory", description: "Toggle durable local project memory", onSelect: () => toggle("projectMemory") },
|
|
105
|
+
{ title: `Learner mode: ${learning ? "On" : "Off"}`, value: "learning", description: "Toggle evidence-based learning tools", onSelect: () => toggle("learning") },
|
|
106
|
+
{ title: `Title settle workaround: ${titleSettle ? "On" : "Off"}`, value: "titleSettleWorkaround", description: "Delay automatic continuation while OpenCode generates a session title", onSelect: () => toggle("titleSettleWorkaround") },
|
|
107
|
+
{ title: `Ralph mode: ${ralphMode ? "On" : "Off"}`, value: "ralphMode", description: "Opt into bounded autonomous continuation of already-owned todos", onSelect: () => toggle("ralphMode") },
|
|
101
108
|
],
|
|
102
|
-
onSelect: (option) => {
|
|
103
|
-
const key = option.value as ProjectToggle;
|
|
104
|
-
const enabled = !readProjectOption(root, key);
|
|
105
|
-
const path = writeProjectOption(root, key, enabled);
|
|
106
|
-
api.ui.dialog.clear();
|
|
107
|
-
api.ui.toast({ variant: "success", title: "Workflow Guard", message: `Saved ${key} ${enabled ? "on" : "off"} in ${path}. Restart OpenCode to apply.` });
|
|
108
|
-
},
|
|
109
109
|
}));
|
|
110
110
|
},
|
|
111
111
|
}],
|
package/src/workflow-guard.ts
CHANGED
|
@@ -669,7 +669,7 @@ export const WorkflowGuard: Plugin = async (ctx) => {
|
|
|
669
669
|
if (recoveryRun !== undefined) {
|
|
670
670
|
finalizeRecoveryCheckpoint(effectiveRoot, sessionID, recoveryRun);
|
|
671
671
|
}
|
|
672
|
-
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround
|
|
672
|
+
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround !== false;
|
|
673
673
|
await runWithRuntimeState(effectiveRoot, ctx.client, () => continueUnfinishedSession(sessionID, settleTitle));
|
|
674
674
|
}
|
|
675
675
|
}
|
package/test/test.mts
CHANGED
|
@@ -1139,20 +1139,21 @@ check("tui plugin registers session_prompt_right slot", typeof registeredSlots.s
|
|
|
1139
1139
|
check("tui plugin registers home_prompt_right slot", typeof registeredSlots.home_prompt_right === "function");
|
|
1140
1140
|
check("tui plugin registers project-options command through keymap", registeredTuiCommands.some((command) => command.name === "workflow-guard.project-options"));
|
|
1141
1141
|
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
1142
|
-
tuiDialogSelectProps?.
|
|
1142
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "recoveryCheckpoints")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
1143
1143
|
check("tui project-options command persists selected recovery setting", readRecoveryCheckpointsOption(tuiCommandOptionsDir) === true);
|
|
1144
1144
|
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
1145
|
-
tuiDialogSelectProps?.
|
|
1145
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "projectMemory")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
1146
1146
|
check("tui project-options command can disable project memory", readProjectOption(tuiCommandOptionsDir, "projectMemory") === false);
|
|
1147
1147
|
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
1148
|
-
tuiDialogSelectProps?.
|
|
1148
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "learning")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
1149
1149
|
check("tui project-options command can enable learner mode", readProjectOption(tuiCommandOptionsDir, "learning") === true);
|
|
1150
1150
|
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
1151
|
-
|
|
1152
|
-
|
|
1151
|
+
check("title settle workaround project option defaults on", readProjectOption(tuiCommandOptionsDir, "titleSettleWorkaround") === true);
|
|
1152
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "titleSettleWorkaround")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
1153
|
+
check("tui project-options command can disable title settle workaround", readProjectOption(tuiCommandOptionsDir, "titleSettleWorkaround") === false);
|
|
1153
1154
|
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
1154
1155
|
check("ralph mode project option defaults off", readProjectOption(tuiCommandOptionsDir, "ralphMode") === false);
|
|
1155
|
-
tuiDialogSelectProps?.
|
|
1156
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "ralphMode")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
1156
1157
|
check("tui project-options command can enable ralph mode", readProjectOption(tuiCommandOptionsDir, "ralphMode") === true);
|
|
1157
1158
|
rmSync(tuiCommandOptionsDir, { recursive: true, force: true });
|
|
1158
1159
|
const tuiOptionsDir = mkdtempSync(join(tmpdir(), "wg-tui-options-"));
|
|
@@ -2841,7 +2842,6 @@ const continuationClient = {
|
|
|
2841
2842
|
};
|
|
2842
2843
|
const continuationRoot = mkdtempSync(join(tmpdir(), "wg-title-settle-"));
|
|
2843
2844
|
mkdirSync(join(continuationRoot, ".opencode"));
|
|
2844
|
-
writeFileSync(join(continuationRoot, ".opencode", "workflow-guard.json"), JSON.stringify({ titleSettleWorkaround: true }));
|
|
2845
2845
|
const continuationPlugin = await WorkflowGuard({ directory: continuationRoot, worktree: continuationRoot, client: continuationClient as any } as any);
|
|
2846
2846
|
todo("s-resume", item("finish work", "pending"));
|
|
2847
2847
|
await continuationPlugin.event?.({ event: { type: "session.idle", properties: { sessionID: "s-resume" } } } as any);
|
|
@@ -2849,20 +2849,22 @@ check("session idle auto-continues unfinished owned todos", continuationPrompts.
|
|
|
2849
2849
|
check("automatic continuation remains synthetic", continuationParts.get("s-resume")?.every((part) => part.synthetic === true) === true);
|
|
2850
2850
|
check("title settle workaround waits for OpenCode's native generated title before continuing", continuationTitleReads.get("s-resume") === 2 && continuationTitles.get("s-resume") === "Native generated title");
|
|
2851
2851
|
|
|
2852
|
-
let
|
|
2853
|
-
let
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2852
|
+
let optOutTitleReads = 0;
|
|
2853
|
+
let optOutPrompts = 0;
|
|
2854
|
+
const optOutRoot = mkdtempSync(join(tmpdir(), "wg-title-settle-off-"));
|
|
2855
|
+
mkdirSync(join(optOutRoot, ".opencode"));
|
|
2856
|
+
writeFileSync(join(optOutRoot, ".opencode", "workflow-guard.json"), JSON.stringify({ titleSettleWorkaround: false }));
|
|
2857
|
+
const optOutPlugin = await WorkflowGuard({ directory: optOutRoot, worktree: optOutRoot, client: {
|
|
2856
2858
|
session: {
|
|
2857
2859
|
todo: async ({ path }: { path: { id: string } }) => ({ data: fakeTodos.get(path.id) ?? [] }),
|
|
2858
|
-
get: async () => {
|
|
2859
|
-
promptAsync: async () => {
|
|
2860
|
+
get: async () => { optOutTitleReads++; return { data: { title: "New session - 2026-08-27T12:00:00.000Z" } }; },
|
|
2861
|
+
promptAsync: async () => { optOutPrompts++; },
|
|
2860
2862
|
},
|
|
2861
2863
|
} as any } as any);
|
|
2862
|
-
todo("s-resume-
|
|
2863
|
-
await
|
|
2864
|
-
check("title settle workaround
|
|
2865
|
-
rmSync(
|
|
2864
|
+
todo("s-resume-opt-out", item("finish without title wait", "pending"));
|
|
2865
|
+
await optOutPlugin.event?.({ event: { type: "session.idle", properties: { sessionID: "s-resume-opt-out" } } } as any);
|
|
2866
|
+
check("title settle workaround explicit false skips title wait without suppressing continuation", optOutTitleReads === 0 && optOutPrompts === 1);
|
|
2867
|
+
rmSync(optOutRoot, { recursive: true, force: true });
|
|
2866
2868
|
|
|
2867
2869
|
todo("s-resume-done", item("finished", "completed"));
|
|
2868
2870
|
await continuationPlugin.event?.({ event: { type: "session.idle", properties: { sessionID: "s-resume-done" } } } as any);
|