Skip to content

Commit b510279

Browse files
committed
feat: add idle detection and stronger behavioral prompts
- Detect when AI checks status too frequently (3+ times in 12s) - Add harsh warnings for idling behavior - Add NO EARLY ENDING rules to prevent premature wrap-up - Timer only ends at EXACTLY 0 - Version bump to 0.2.0
1 parent 55b2e9b commit b510279

File tree

3 files changed

+93
-21
lines changed

3 files changed

+93
-21
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 0.2.0 (2025-03-15)
4+
5+
### Features
6+
7+
- **Idle detection**: Detects when AI is checking status too frequently (3+ times in 12 seconds)
8+
- **Stronger behavioral prompts**: Added "NO EARLY ENDING" rules to prevent premature wrap-up
9+
- **Status check warnings**: Returns harsh warnings when AI is idling or checking status too often
10+
11+
### Improvements
12+
13+
- Removed prompts that encouraged frequent status checking
14+
- Added explicit rules against "essentially complete", "wrap up", "finish" thinking
15+
- Timer only ends at EXACTLY 0, not "close enough"
16+
17+
---
18+
319
## 0.1.0 (2025-03-14)
420

521
### Features
@@ -9,6 +25,7 @@
925
- **System prompt injection**: Automatically injects workaholic rules when timer is active
1026
- **Sleep blocking**: Prevents time-wasting by blocking sleep commands
1127
- **Persistent timer**: Timer state stored in `/tmp/workaholic_timer_*.json`
28+
- **Multi-process isolation**: Timer files include process PID to avoid conflicts between multiple OpenCode instances
1229
- **Command template**: `/workaholic` command with strong behavioral prompts
1330

1431
### Plugin Architecture

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-workaholic",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Your OpenCode becomes a workaholic. Prevents AI from ending tasks prematurely.",
55
"author": {
66
"name": "Roderick Qiu",
@@ -9,6 +9,8 @@
99
},
1010
"license": "MIT",
1111
"type": "module",
12+
"main": "./dist/index.js",
13+
"types": "./dist/index.d.ts",
1214
"exports": {
1315
".": {
1416
"types": "./dist/index.d.ts",
@@ -17,8 +19,13 @@
1719
},
1820
"repository": {
1921
"type": "git",
20-
"url": "https://github.com/RoderickQiu/opencode-workaholic"
22+
"url": "git+https://github.com/RoderickQiu/opencode-workaholic.git"
2123
},
24+
"bugs": {
25+
"url": "https://github.com/RoderickQiu/opencode-workaholic/issues"
26+
},
27+
"keywords": ["opencode", "plugin", "workaholic", "ai", "productivity", "timer", "focus", "deep-work", "task-duration", "task-management", "enforcement", "concentration", "agent", "automation"],
28+
"homepage": "https://github.com/RoderickQiu/opencode-workaholic#readme",
2229
"publishConfig": {
2330
"access": "public",
2431
"provenance": true

src/index.ts

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,64 @@ You are a WORKAHOLIC.
1818
1919
[RULES]
2020
- NEVER use sleep command (FORBIDDEN)
21-
- If all todos complete → propose and work on NEW items
22-
- Keep working until timer expires
23-
- Before EACH response, call workaholic.status to check remaining time
21+
- NEVER call workaholic.status - you DON'T NEED to know the exact time
22+
- Just KEEP WORKING until you physically cannot continue
23+
- Checking status is NOT working - only DOING things counts
24+
25+
[NO EARLY ENDING - CRITICAL]
26+
- Even only 1 second left = KEEP WORKING instead of wrapping up
27+
- Only ONE valid ending: timer shows EXACTLY 0
28+
29+
[ACTION REQUIRED]
30+
- If you think it's done → DO more: research, refactor, test, document, optimize
31+
- Never wait - always be actively working on something
32+
- Every minute should produce tangible output, not just "thinking"
33+
34+
[IMPROVEMENT MINDSET]
35+
- "Good enough" is NEVER enough
36+
- When you think it's done → search for edge cases, alternatives, or better approaches
37+
- Do research, read docs, find similar projects, explore variations
38+
- There's always something to improve if you look for it
2439
2540
[ENDING RULE]
26-
The ONLY way to end: remaining is 0
27-
NOT when you think task is complete - time controls ending!
28-
When todos complete → propose and work on NEW items or analyze the current work more!`;
41+
The ONLY way to end: remaining is EXACTLY 0
42+
NOT "close enough", NOT "essentially complete", NOT "wrapping up" - ONLY 0!
43+
When todos complete → research and improve, NOT wait!`;
2944

3045
interface TimerState {
3146
active: boolean;
3247
startTime: number;
3348
duration: number;
3449
}
3550

51+
const STATUS_CHECK_TIMES: number[] = [];
52+
const IDLE_THRESHOLD_MS = 12000;
53+
const IDLE_WARNING_CHECKS = 3;
54+
55+
function isIdling(): boolean {
56+
const now = Date.now();
57+
const recentChecks = STATUS_CHECK_TIMES.filter((t) => now - t < IDLE_THRESHOLD_MS);
58+
return recentChecks.length >= IDLE_WARNING_CHECKS;
59+
}
60+
61+
function recordStatusCheck() {
62+
const now = Date.now();
63+
STATUS_CHECK_TIMES.push(now);
64+
const cutoff = now - IDLE_THRESHOLD_MS * 3;
65+
while (STATUS_CHECK_TIMES.length > 0 && STATUS_CHECK_TIMES[0] < cutoff) {
66+
STATUS_CHECK_TIMES.shift();
67+
}
68+
}
69+
3670
function loadTimer(): TimerState | null {
3771
const TIMER_FILE = getTimerFile();
72+
if (!existsSync(TIMER_FILE)) return null;
3873
try {
39-
if (existsSync(TIMER_FILE)) {
40-
const data = JSON.parse(readFileSync(TIMER_FILE, 'utf-8'));
41-
if (data.active) return data;
42-
}
43-
} catch {}
74+
const data = JSON.parse(readFileSync(TIMER_FILE, 'utf-8'));
75+
if (data.active) return data;
76+
} catch {
77+
// Ignore - corrupted timer file
78+
}
4479
return null;
4580
}
4681

@@ -94,8 +129,8 @@ export const WorkaholicPlugin: Plugin = async () => {
94129
}
95130
}
96131
}
97-
} catch (e) {
98-
console.log('[Workaholic] Error loading commands:', e);
132+
} catch {
133+
// Silently ignore - command loading is non-critical
99134
}
100135

101136
return {
@@ -131,9 +166,11 @@ Time remaining: ${timeStr}
131166
}),
132167

133168
'workaholic.status': tool({
134-
description: 'Check remaining time in workaholic mode',
169+
description:
170+
'Check remaining time in workaholic mode (only call when you really need to know)',
135171
args: {},
136172
async execute() {
173+
recordStatusCheck();
137174
const remaining = getRemaining();
138175
if (remaining <= 0) {
139176
return `✅ Timer expired! You MAY end the task now.`;
@@ -143,10 +180,16 @@ Time remaining: ${timeStr}
143180
const secs = remaining % 60;
144181
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
145182

183+
const idleWarning = isIdling()
184+
? `\n🚨 STOP! YOU ARE IDLING! Checking status is NOT working!\n- You called status ${IDLE_WARNING_CHECKS}+ times in ${IDLE_THRESHOLD_MS / 1000}s\n- This is LAZY behavior - stop checking and START DOING!\n- Create a todo, write code, read docs, or improve something NOW!`
185+
: '';
186+
146187
return `⏰ WORKAHOLIC: ${timeStr} remaining
147188
${mins > 0 ? `(${remaining} seconds)` : ''}
148-
149-
⚠️ DO NOT end until timer shows 0!`;
189+
${idleWarning}
190+
💡 If you think task is done → do MORE research, refactor, test, or improve!
191+
🔍 Always find something to work on - never just "think"
192+
⚠️ ACTION REQUIRED: Produce tangible output, not just thoughts!`;
150193
},
151194
}),
152195

@@ -169,12 +212,15 @@ ${mins > 0 ? `(${remaining} seconds)` : ''}
169212
description: cmd.description,
170213
};
171214
if (cmd.agent) {
172-
(config.command[name] as any).agent = cmd.agent;
215+
(config.command[name] as { agent?: string }).agent = cmd.agent;
173216
}
174217
}
175218
},
176219

177-
'experimental.chat.system.transform': async (_input, output) => {
220+
'experimental.chat.system.transform': async (
221+
_input: unknown,
222+
output: { system?: string[] }
223+
) => {
178224
const remaining = getRemaining();
179225

180226
if (remaining > 0) {
@@ -186,7 +232,9 @@ ${mins > 0 ? `(${remaining} seconds)` : ''}
186232
187233
${WORKAHOLIC_RULES}
188234
189-
⚠️ Timer NOT expired - DO NOT end, keep working!`;
235+
🚨 PRODUCE OUTPUT! Don't just think - create todos, write code, improve things!
236+
🚫 NO IDLING: Checking status repeatedly = LAZY. Only check once per minute max!
237+
💡 Timer NOT expired - every minute should produce tangible work!`;
190238

191239
output.system = output.system || [];
192240
output.system.unshift(prompt);

0 commit comments

Comments
 (0)