opencodekit 0.16.15 → 0.16.18
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 +77 -242
- package/dist/index.js +19 -6
- package/dist/template/.opencode/AGENTS.md +72 -236
- package/dist/template/.opencode/README.md +49 -482
- package/dist/template/.opencode/agent/build.md +71 -345
- package/dist/template/.opencode/agent/explore.md +47 -139
- package/dist/template/.opencode/agent/general.md +61 -172
- package/dist/template/.opencode/agent/looker.md +65 -161
- package/dist/template/.opencode/agent/painter.md +46 -200
- package/dist/template/.opencode/agent/plan.md +37 -220
- package/dist/template/.opencode/agent/review.md +72 -153
- package/dist/template/.opencode/agent/scout.md +44 -486
- package/dist/template/.opencode/agent/vision.md +63 -178
- package/dist/template/.opencode/command/create.md +75 -307
- package/dist/template/.opencode/command/design.md +53 -589
- package/dist/template/.opencode/command/handoff.md +76 -180
- package/dist/template/.opencode/command/init.md +45 -211
- package/dist/template/.opencode/command/plan.md +62 -514
- package/dist/template/.opencode/command/pr.md +56 -226
- package/dist/template/.opencode/command/research.md +55 -266
- package/dist/template/.opencode/command/resume.md +33 -138
- package/dist/template/.opencode/command/review-codebase.md +54 -202
- package/dist/template/.opencode/command/ship.md +78 -127
- package/dist/template/.opencode/command/start.md +47 -577
- package/dist/template/.opencode/command/status.md +60 -353
- package/dist/template/.opencode/command/ui-review.md +52 -298
- package/dist/template/.opencode/command/verify.md +36 -250
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +133 -35
- package/dist/template/.opencode/plugin/README.md +40 -166
- package/dist/template/.opencode/plugin/compaction.ts +162 -131
- package/dist/template/.opencode/plugin/lib/memory-db.ts +112 -0
- package/dist/template/.opencode/plugin/swarm-enforcer.ts +182 -27
- package/dist/template/.opencode/skill/augment-context-engine/SKILL.md +112 -0
- package/dist/template/.opencode/skill/augment-context-engine/mcp.json +6 -0
- package/dist/template/.opencode/skill/core-data-expert/SKILL.md +82 -0
- package/dist/template/.opencode/skill/core-data-expert/references/batch-operations.md +543 -0
- package/dist/template/.opencode/skill/core-data-expert/references/cloudkit-integration.md +259 -0
- package/dist/template/.opencode/skill/core-data-expert/references/concurrency.md +522 -0
- package/dist/template/.opencode/skill/core-data-expert/references/fetch-requests.md +643 -0
- package/dist/template/.opencode/skill/core-data-expert/references/glossary.md +233 -0
- package/dist/template/.opencode/skill/core-data-expert/references/migration.md +393 -0
- package/dist/template/.opencode/skill/core-data-expert/references/model-configuration.md +597 -0
- package/dist/template/.opencode/skill/core-data-expert/references/performance.md +300 -0
- package/dist/template/.opencode/skill/core-data-expert/references/persistent-history.md +553 -0
- package/dist/template/.opencode/skill/core-data-expert/references/project-audit.md +60 -0
- package/dist/template/.opencode/skill/core-data-expert/references/saving.md +574 -0
- package/dist/template/.opencode/skill/core-data-expert/references/stack-setup.md +625 -0
- package/dist/template/.opencode/skill/core-data-expert/references/testing.md +300 -0
- package/dist/template/.opencode/skill/core-data-expert/references/threading.md +589 -0
- package/dist/template/.opencode/skill/swift-concurrency/SKILL.md +246 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/actors.md +640 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/async-algorithms.md +822 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/async-await-basics.md +249 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/async-sequences.md +670 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/core-data.md +533 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/glossary.md +128 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/linting.md +142 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/memory-management.md +542 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/migration.md +1076 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/performance.md +574 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/sendable.md +578 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/tasks.md +604 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/testing.md +565 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/threading.md +452 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/SKILL.md +290 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-advanced.md +351 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-basics.md +284 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-transitions.md +326 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/image-optimization.md +286 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/layout-best-practices.md +312 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/liquid-glass.md +377 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/list-patterns.md +153 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/modern-apis.md +400 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/performance-patterns.md +377 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/scroll-patterns.md +305 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/sheet-navigation-patterns.md +292 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/state-management.md +447 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/text-formatting.md +285 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/view-structure.md +276 -0
- package/dist/template/.opencode/tool/action-queue.ts +308 -0
- package/dist/template/.opencode/tool/swarm.ts +65 -40
- package/package.json +16 -3
- package/dist/template/.opencode/.agents/skills/context7/SKILL.md +0 -88
|
@@ -1,184 +1,58 @@
|
|
|
1
1
|
# OpenCode Plugins
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plugins in this directory extend OpenCode with project-specific behavior and tools.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Current Plugin Files
|
|
6
6
|
|
|
7
|
-
```
|
|
7
|
+
```text
|
|
8
8
|
plugin/
|
|
9
|
+
├── memory.ts # Memory DB maintenance + observation toasts
|
|
10
|
+
├── sessions.ts # Session tools (list/read/search/summarize)
|
|
11
|
+
├── compaction.ts # Compaction-time context recovery injection
|
|
12
|
+
├── swarm-enforcer.ts # Beads workflow enforcement and reminders
|
|
13
|
+
├── skill-mcp.ts # Skill-scoped MCP bridge (skill_mcp tools)
|
|
14
|
+
├── copilot-auth.ts # GitHub Copilot provider/auth integration
|
|
9
15
|
├── lib/
|
|
10
|
-
│
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
├── enforcer.ts # TODO completion enforcement
|
|
14
|
-
├── notification.ts # Session completion alerts
|
|
15
|
-
├── sessions.ts # Session management tools
|
|
16
|
-
├── truncator.ts # Output size monitoring
|
|
17
|
-
└── README.md
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Installed Plugins
|
|
21
|
-
|
|
22
|
-
### injector.ts
|
|
23
|
-
|
|
24
|
-
**AGENTS.md hierarchy walker** - solves OpenCode's limitation where findUp only finds the first AGENTS.md match.
|
|
25
|
-
|
|
26
|
-
- Hooks into `tool.execute.after` for `read` tool
|
|
27
|
-
- Walks up from file directory to project root
|
|
28
|
-
- Collects ALL AGENTS.md files in the path
|
|
29
|
-
- Injects in order: root → specific (T-shaped context loading)
|
|
30
|
-
- Caches per session to avoid duplicate injections
|
|
31
|
-
|
|
32
|
-
**Example:** When reading `src/components/Button.tsx`:
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
Injects:
|
|
36
|
-
1. /project/AGENTS.md (root context)
|
|
37
|
-
2. /project/src/AGENTS.md (src context)
|
|
38
|
-
3. /project/src/components/AGENTS.md (component context)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### sessions.ts
|
|
42
|
-
|
|
43
|
-
**Session management and context transfer** - enables short, focused sessions.
|
|
44
|
-
|
|
45
|
-
**Tools:**
|
|
46
|
-
|
|
47
|
-
- `list_sessions(project?, since?, limit?)` - Discover available sessions
|
|
48
|
-
- `read_session(session_reference, project?, focus?)` - Load context from previous sessions
|
|
49
|
-
- `summarize_session(session_id)` - Generate AI summary of a session
|
|
50
|
-
|
|
51
|
-
**Workflow pattern:**
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
Session 1: Implementation (80k) → close
|
|
55
|
-
Session 2: read_session("last") → Refactor (60k) → close
|
|
56
|
-
Session 3: read_session("previous") → Tests (90k) → close
|
|
16
|
+
│ ├── memory-db.ts # SQLite + FTS5 memory backend
|
|
17
|
+
│ └── notify.ts # Shared notification helpers
|
|
18
|
+
└── sdk/ # Copilot SDK adaptation code
|
|
57
19
|
```
|
|
58
20
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
**Context usage warnings** - notifies at token thresholds before hitting limits.
|
|
62
|
-
|
|
63
|
-
- Warns at 70% (info), 85% (warn), 95% (critical)
|
|
64
|
-
- Sends native notifications at 85%+
|
|
65
|
-
- Tracks per-session to avoid duplicate warnings
|
|
66
|
-
|
|
67
|
-
### enforcer.ts
|
|
68
|
-
|
|
69
|
-
**TODO completion enforcement** - forces continuation when session idles with incomplete work.
|
|
70
|
-
|
|
71
|
-
- Tracks TODOs per session via `todo.updated` events
|
|
72
|
-
- On `session.idle`, checks for incomplete high-priority or in-progress TODOs
|
|
73
|
-
- **ENFORCES** continuation by calling `client.session.promptAsync()` (not just notification)
|
|
74
|
-
- Injects prompt: "Continue working on incomplete TODOs: [list]"
|
|
75
|
-
- 5-minute cooldown to prevent spam
|
|
76
|
-
- Falls back to OS notification if prompt injection fails
|
|
77
|
-
|
|
78
|
-
**Behavior:**
|
|
79
|
-
|
|
80
|
-
- High-priority or in-progress TODOs → Inject continuation prompt
|
|
81
|
-
- Low-priority pending TODOs → OS notification only (no forced continuation)
|
|
21
|
+
## Plugin Responsibilities
|
|
82
22
|
|
|
83
|
-
|
|
23
|
+
- `memory.ts`
|
|
24
|
+
- Optimizes FTS5 index on idle sessions
|
|
25
|
+
- Checkpoints WAL when needed
|
|
26
|
+
- Shows toast feedback for observation saves and session errors
|
|
84
27
|
|
|
85
|
-
|
|
28
|
+
- `sessions.ts`
|
|
29
|
+
- Provides custom tools: `list_sessions`, `read_session`, `search_session`, `summarize_session`
|
|
86
30
|
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
- On `file.edited`, warns if code is being changed without a claimed task or with missing `spec.md`
|
|
91
|
-
- On `session.idle`, reminds to `br close` + `br sync` when work is done
|
|
31
|
+
- `compaction.ts`
|
|
32
|
+
- Injects session continuity context during compaction
|
|
33
|
+
- Pulls memory/project/handoff context and recovery instructions
|
|
92
34
|
|
|
93
|
-
|
|
35
|
+
- `swarm-enforcer.ts`
|
|
36
|
+
- Injects bead state and stage labels into system context
|
|
37
|
+
- Warns when implementation starts without a properly started bead
|
|
38
|
+
- Reminds to close/sync in-progress work on session idle
|
|
94
39
|
|
|
95
|
-
|
|
40
|
+
- `skill-mcp.ts`
|
|
41
|
+
- Loads MCP configs from skills
|
|
42
|
+
- Exposes `skill_mcp`, `skill_mcp_status`, `skill_mcp_disconnect`
|
|
43
|
+
- Supports tool filtering with `includeTools`
|
|
96
44
|
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
### truncator.ts
|
|
102
|
-
|
|
103
|
-
**Output size monitoring** - logs warnings for large outputs under context pressure.
|
|
104
|
-
|
|
105
|
-
- Monitors `tool.execute.after` events
|
|
106
|
-
- Warns when outputs exceed thresholds based on context usage
|
|
107
|
-
- Note: Actual truncation requires OpenCode core changes; this is observation-only
|
|
108
|
-
|
|
109
|
-
## Shared Library
|
|
110
|
-
|
|
111
|
-
### lib/notify.ts
|
|
112
|
-
|
|
113
|
-
Shared utilities used by multiple plugins:
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import { notify, THRESHOLDS, getContextPercentage } from "./lib/notify";
|
|
117
|
-
|
|
118
|
-
// Send cross-platform notification using $ shell API
|
|
119
|
-
await notify($, "Title", "Message");
|
|
120
|
-
|
|
121
|
-
// Context thresholds
|
|
122
|
-
THRESHOLDS.MODERATE; // 70%
|
|
123
|
-
THRESHOLDS.URGENT; // 85%
|
|
124
|
-
THRESHOLDS.CRITICAL; // 95%
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Best Practices Applied
|
|
128
|
-
|
|
129
|
-
### Use `$` Shell API (not `exec`)
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// ✅ Correct - uses Bun shell from plugin context
|
|
133
|
-
export const MyPlugin: Plugin = async ({ $ }) => {
|
|
134
|
-
await $`osascript -e 'display notification "Done!"'`;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// ❌ Wrong - manual exec with escaping
|
|
138
|
-
import { exec } from "child_process";
|
|
139
|
-
exec(`osascript -e '...'`, () => {});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Share Common Code
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
// ✅ Correct - import from shared lib
|
|
146
|
-
import { notify } from "./lib/notify";
|
|
147
|
-
|
|
148
|
-
// ❌ Wrong - copy-paste same code in every plugin
|
|
149
|
-
function notify() {
|
|
150
|
-
/* duplicated */
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Proper Plugin Structure
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
158
|
-
|
|
159
|
-
export const MyPlugin: Plugin = async ({ client, $ }) => {
|
|
160
|
-
return {
|
|
161
|
-
event: async ({ event }) => {
|
|
162
|
-
/* ... */
|
|
163
|
-
},
|
|
164
|
-
"tool.execute.after": async (input, output) => {
|
|
165
|
-
/* ... */
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
};
|
|
169
|
-
```
|
|
45
|
+
- `copilot-auth.ts`
|
|
46
|
+
- Handles GitHub Copilot OAuth/device flow
|
|
47
|
+
- Adds model/provider request shaping for compatible reasoning behavior
|
|
170
48
|
|
|
171
|
-
##
|
|
49
|
+
## Notes
|
|
172
50
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
| `tool.execute.before` | Hook before tool execution |
|
|
177
|
-
| `tool.execute.after` | Hook after tool execution (observation only) |
|
|
178
|
-
| `tool` | Add custom tools |
|
|
51
|
+
- `notification.ts.bak` is a backup file and not part of the active plugin set.
|
|
52
|
+
- Keep plugin documentation aligned with actual files in this directory.
|
|
53
|
+
- Prefer shared helpers in `lib/` over duplicated utilities across plugins.
|
|
179
54
|
|
|
180
|
-
##
|
|
55
|
+
## References
|
|
181
56
|
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
- [Community Examples](https://github.com/sst/opencode/discussions)
|
|
57
|
+
- OpenCode plugin docs: https://opencode.ai/docs/plugins/
|
|
58
|
+
- OpenCode custom tools docs: https://opencode.ai/docs/custom-tools/
|
|
@@ -1,152 +1,183 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session Continuity Plugin (
|
|
2
|
+
* Session Continuity Plugin (compaction-time context injection)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Provide compact, bounded state needed to resume work after compaction.
|
|
6
|
+
* - Keep injected prompt guidance minimal and deterministic.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 4. Load most recent handoff file for session resumption
|
|
12
|
-
* 5. Append post-compaction recovery protocol
|
|
13
|
-
*
|
|
14
|
-
* Division of responsibility:
|
|
15
|
-
* - DCP plugin: context budget zones, distill/compress/prune rules,
|
|
16
|
-
* prunable-tools list, nudge system (always-on via system.transform)
|
|
17
|
-
* - This plugin: session state preservation, post-compaction recovery,
|
|
18
|
-
* memory persistence reminders (compaction-time only)
|
|
8
|
+
* Non-goals:
|
|
9
|
+
* - Replace DCP policy/rules management.
|
|
10
|
+
* - Inject large free-form manuals into the prompt.
|
|
19
11
|
*/
|
|
20
12
|
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { promisify } from "node:util";
|
|
21
17
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
20
|
+
|
|
21
|
+
const MAX_SESSION_CONTEXT_CHARS = 3000;
|
|
22
|
+
const MAX_PROJECT_FILES = 3;
|
|
23
|
+
const MAX_PROJECT_FILE_CHARS = 900;
|
|
24
|
+
const MAX_HANDOFF_CHARS = 2500;
|
|
25
|
+
const MAX_BEADS = 8;
|
|
26
|
+
const MAX_COMBINED_CONTEXT_CHARS = 9000;
|
|
27
|
+
|
|
28
|
+
function truncate(text: string, maxChars: number): string {
|
|
29
|
+
if (text.length <= maxChars) return text;
|
|
30
|
+
return `${text.slice(0, maxChars)}\n...[truncated]`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function safeReadFile(filePath: string): Promise<string> {
|
|
34
|
+
try {
|
|
35
|
+
return await readFile(filePath, "utf-8");
|
|
36
|
+
} catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function renderSection(title: string, body: string): string {
|
|
42
|
+
if (!body.trim()) return "";
|
|
43
|
+
return `## ${title}\n${body.trim()}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readProjectMemoryContext(memoryDir: string): Promise<string> {
|
|
47
|
+
const projectDir = path.join(memoryDir, "project");
|
|
48
|
+
let names: string[] = [];
|
|
49
|
+
try {
|
|
50
|
+
names = (await readdir(projectDir))
|
|
51
|
+
.filter((name) => name.endsWith(".md"))
|
|
52
|
+
.sort()
|
|
53
|
+
.slice(0, MAX_PROJECT_FILES);
|
|
54
|
+
} catch {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const chunks: string[] = [];
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
const fullPath = path.join(projectDir, name);
|
|
61
|
+
const content = (await safeReadFile(fullPath)).trim();
|
|
62
|
+
if (!content) continue;
|
|
63
|
+
chunks.push(
|
|
64
|
+
`### ${name.replace(/\.md$/, "")}\n${truncate(content, MAX_PROJECT_FILE_CHARS)}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return chunks.join("\n\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function readLatestHandoff(handoffDir: string): Promise<string> {
|
|
72
|
+
let names: string[] = [];
|
|
73
|
+
try {
|
|
74
|
+
names = (await readdir(handoffDir)).filter((name) => name.endsWith(".md"));
|
|
75
|
+
} catch {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (names.length === 0) return "";
|
|
80
|
+
|
|
81
|
+
const withMtime = await Promise.all(
|
|
82
|
+
names.map(async (name) => {
|
|
83
|
+
const fullPath = path.join(handoffDir, name);
|
|
77
84
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
if (result.stdout) {
|
|
81
|
-
const handoffPath = result.stdout.toString().trim();
|
|
82
|
-
if (handoffPath) {
|
|
83
|
-
const handoffContent = await $`cat ${handoffPath}`.text();
|
|
84
|
-
handoffContext = `\n## Previous Session Handoff\n\n${handoffContent}\n\n**IMPORTANT**: Resume work from where previous session left off.`;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
85
|
+
const info = await stat(fullPath);
|
|
86
|
+
return { name, fullPath, mtimeMs: info.mtimeMs };
|
|
87
87
|
} catch {
|
|
88
|
-
|
|
88
|
+
return { name, fullPath, mtimeMs: 0 };
|
|
89
89
|
}
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
94
|
+
const latest = withMtime[0];
|
|
95
|
+
const content = (await safeReadFile(latest.fullPath)).trim();
|
|
96
|
+
if (!content) return "";
|
|
97
|
+
|
|
98
|
+
return `Source: ${latest.name}\n${truncate(content, MAX_HANDOFF_CHARS)}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function readInProgressBeads(directory: string): Promise<string> {
|
|
102
|
+
try {
|
|
103
|
+
const { stdout } = await execFileAsync(
|
|
104
|
+
"br",
|
|
105
|
+
["list", "--status", "in_progress", "--json"],
|
|
106
|
+
{
|
|
107
|
+
cwd: directory,
|
|
108
|
+
timeout: 10000,
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const parsed = JSON.parse(stdout as string) as Array<{
|
|
113
|
+
id: string;
|
|
114
|
+
title: string;
|
|
115
|
+
}>;
|
|
116
|
+
|
|
117
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return "";
|
|
118
|
+
|
|
119
|
+
return parsed
|
|
120
|
+
.slice(0, MAX_BEADS)
|
|
121
|
+
.map((item) => `- ${item.id}: ${item.title}`)
|
|
122
|
+
.join("\n");
|
|
123
|
+
} catch {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const CompactionPlugin: Plugin = async ({ directory }) => {
|
|
129
|
+
const memoryDir = path.join(directory, ".opencode", "memory");
|
|
130
|
+
const handoffDir = path.join(memoryDir, "handoffs");
|
|
90
131
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
132
|
+
return {
|
|
133
|
+
"experimental.session.compacting": async (_input, output) => {
|
|
134
|
+
const sessionContext = truncate(
|
|
135
|
+
(await safeReadFile(path.join(memoryDir, "session-context.md"))).trim(),
|
|
136
|
+
MAX_SESSION_CONTEXT_CHARS,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const [projectContext, beadsContext, handoffContext] = await Promise.all([
|
|
140
|
+
readProjectMemoryContext(memoryDir),
|
|
141
|
+
readInProgressBeads(directory),
|
|
142
|
+
readLatestHandoff(handoffDir),
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const combined = [
|
|
146
|
+
renderSection("Session Continuity", sessionContext),
|
|
147
|
+
renderSection("Active Beads", beadsContext),
|
|
148
|
+
renderSection("Previous Handoff", handoffContext),
|
|
149
|
+
renderSection("Project Memory", projectContext),
|
|
97
150
|
]
|
|
98
151
|
.filter(Boolean)
|
|
99
|
-
.join("\n");
|
|
152
|
+
.join("\n\n");
|
|
100
153
|
|
|
101
|
-
if (
|
|
102
|
-
output.context.push(
|
|
154
|
+
if (combined) {
|
|
155
|
+
output.context.push(
|
|
156
|
+
`## Session Context\n${truncate(combined, MAX_COMBINED_CONTEXT_CHARS)}\n`,
|
|
157
|
+
);
|
|
103
158
|
}
|
|
104
159
|
|
|
105
|
-
// Append post-compaction recovery protocol
|
|
106
|
-
// NOTE: Context management rules (distill/compress/prune, tool guidance)
|
|
107
|
-
// are handled by DCP plugin via experimental.chat.system.transform (always present).
|
|
108
|
-
// This plugin ONLY handles session continuity and post-compaction recovery.
|
|
109
160
|
output.prompt = `${output.prompt}
|
|
110
161
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Open Questions: [Uncertainties - mark UNCONFIRMED, note what would resolve each]
|
|
131
|
-
Working Set: [Exact file paths, bead IDs, branch name, worktree path if applicable]
|
|
132
|
-
\`\`\`
|
|
133
|
-
|
|
134
|
-
Update session-context.md when: goal changes, key decision made, state shifts, or uncertainty discovered.
|
|
135
|
-
|
|
136
|
-
### Preservation Priorities (Compaction-Specific)
|
|
137
|
-
These items MUST survive compaction — if they appeared in conversation, include in summary:
|
|
138
|
-
- **Bead state**: IDs (br-xxx), status, in-progress tasks, dependencies
|
|
139
|
-
- **Todo items**: Exact IDs, statuses, priorities, content
|
|
140
|
-
- **File paths**: Exact paths with line numbers where relevant
|
|
141
|
-
- **User constraints**: Verbatim. Never paraphrase.
|
|
142
|
-
- **Decisions + rationale**: Both WHAT and WHY
|
|
143
|
-
- **UNCONFIRMED items**: Keep uncertainty markers intact
|
|
144
|
-
|
|
145
|
-
### Memory Persistence (Before Compaction Loses It)
|
|
146
|
-
Save discoveries to persistent memory so they survive:
|
|
147
|
-
- Gotchas/edge cases → observation({ type: "warning", ... })
|
|
148
|
-
- Architecture insights → observation({ type: "pattern", ... })
|
|
149
|
-
- Key decisions → observation({ type: "decision", ... })
|
|
162
|
+
<compaction_task>
|
|
163
|
+
Summarize conversation state for reliable continuation after compaction.
|
|
164
|
+
</compaction_task>
|
|
165
|
+
|
|
166
|
+
<compaction_rules>
|
|
167
|
+
- Preserve exact IDs, file paths, and unresolved constraints.
|
|
168
|
+
- Distinguish completed work from current in-progress work.
|
|
169
|
+
- Keep summary concise and execution-focused.
|
|
170
|
+
- If critical context is missing, state uncertainty explicitly.
|
|
171
|
+
</compaction_rules>
|
|
172
|
+
|
|
173
|
+
<compaction_output>
|
|
174
|
+
Include:
|
|
175
|
+
- What was done
|
|
176
|
+
- What is being worked on now
|
|
177
|
+
- Files currently in play
|
|
178
|
+
- Next actions
|
|
179
|
+
- Persistent user constraints/preferences
|
|
180
|
+
</compaction_output>
|
|
150
181
|
`;
|
|
151
182
|
},
|
|
152
183
|
};
|
|
@@ -85,6 +85,31 @@ export interface MemoryFileRow {
|
|
|
85
85
|
updated_at_epoch: number | null;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export type ActionQueueSource = "approval" | "bead" | "worker";
|
|
89
|
+
export type ActionQueueStatus = "pending" | "ready" | "idle";
|
|
90
|
+
|
|
91
|
+
export interface ActionQueueItemRow {
|
|
92
|
+
id: string;
|
|
93
|
+
source: ActionQueueSource;
|
|
94
|
+
status: ActionQueueStatus;
|
|
95
|
+
title: string;
|
|
96
|
+
owner: string | null;
|
|
97
|
+
payload: string | null;
|
|
98
|
+
created_at: string;
|
|
99
|
+
created_at_epoch: number;
|
|
100
|
+
updated_at: string | null;
|
|
101
|
+
updated_at_epoch: number | null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ActionQueueItemInput {
|
|
105
|
+
id: string;
|
|
106
|
+
source: ActionQueueSource;
|
|
107
|
+
status: ActionQueueStatus;
|
|
108
|
+
title: string;
|
|
109
|
+
owner?: string;
|
|
110
|
+
payload?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
|
|
88
113
|
// ============================================================================
|
|
89
114
|
// Schema
|
|
90
115
|
// ============================================================================
|
|
@@ -153,6 +178,23 @@ CREATE TABLE IF NOT EXISTS memory_files (
|
|
|
153
178
|
);
|
|
154
179
|
|
|
155
180
|
CREATE INDEX IF NOT EXISTS idx_memory_files_path ON memory_files(file_path);
|
|
181
|
+
|
|
182
|
+
-- Action queue table for orchestration status snapshots
|
|
183
|
+
CREATE TABLE IF NOT EXISTS action_queue_items (
|
|
184
|
+
id TEXT PRIMARY KEY,
|
|
185
|
+
source TEXT NOT NULL CHECK(source IN ('approval','bead','worker')),
|
|
186
|
+
status TEXT NOT NULL CHECK(status IN ('pending','ready','idle')),
|
|
187
|
+
title TEXT NOT NULL,
|
|
188
|
+
owner TEXT,
|
|
189
|
+
payload TEXT,
|
|
190
|
+
created_at TEXT NOT NULL,
|
|
191
|
+
created_at_epoch INTEGER NOT NULL,
|
|
192
|
+
updated_at TEXT,
|
|
193
|
+
updated_at_epoch INTEGER
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
CREATE INDEX IF NOT EXISTS idx_action_queue_status ON action_queue_items(status);
|
|
197
|
+
CREATE INDEX IF NOT EXISTS idx_action_queue_source ON action_queue_items(source);
|
|
156
198
|
`;
|
|
157
199
|
|
|
158
200
|
// FTS5 sync triggers (separate because they can't use IF NOT EXISTS)
|
|
@@ -574,6 +616,76 @@ export function getMemoryFile(filePath: string): MemoryFileRow | null {
|
|
|
574
616
|
.get(filePath) as MemoryFileRow | null;
|
|
575
617
|
}
|
|
576
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Replace action queue snapshot with a new set of items.
|
|
621
|
+
*/
|
|
622
|
+
export function replaceActionQueueItems(items: ActionQueueItemInput[]): void {
|
|
623
|
+
const db = getMemoryDB();
|
|
624
|
+
const now = new Date();
|
|
625
|
+
|
|
626
|
+
const insertStmt = db.query(
|
|
627
|
+
`
|
|
628
|
+
INSERT INTO action_queue_items
|
|
629
|
+
(id, source, status, title, owner, payload, created_at, created_at_epoch)
|
|
630
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
631
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
632
|
+
source = excluded.source,
|
|
633
|
+
status = excluded.status,
|
|
634
|
+
title = excluded.title,
|
|
635
|
+
owner = excluded.owner,
|
|
636
|
+
payload = excluded.payload,
|
|
637
|
+
updated_at = excluded.created_at,
|
|
638
|
+
updated_at_epoch = excluded.created_at_epoch
|
|
639
|
+
`,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
db.transaction(() => {
|
|
643
|
+
db.run("DELETE FROM action_queue_items");
|
|
644
|
+
for (const item of items) {
|
|
645
|
+
insertStmt.run(
|
|
646
|
+
item.id,
|
|
647
|
+
item.source,
|
|
648
|
+
item.status,
|
|
649
|
+
item.title,
|
|
650
|
+
item.owner ?? null,
|
|
651
|
+
item.payload ? JSON.stringify(item.payload) : null,
|
|
652
|
+
now.toISOString(),
|
|
653
|
+
now.getTime(),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
})();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Return action queue items, optionally filtered by status.
|
|
661
|
+
*/
|
|
662
|
+
export function listActionQueueItems(
|
|
663
|
+
status?: ActionQueueStatus,
|
|
664
|
+
): ActionQueueItemRow[] {
|
|
665
|
+
const db = getMemoryDB();
|
|
666
|
+
if (!status) {
|
|
667
|
+
return db
|
|
668
|
+
.query(
|
|
669
|
+
"SELECT * FROM action_queue_items ORDER BY created_at_epoch DESC, id ASC",
|
|
670
|
+
)
|
|
671
|
+
.all() as ActionQueueItemRow[];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return db
|
|
675
|
+
.query(
|
|
676
|
+
"SELECT * FROM action_queue_items WHERE status = ? ORDER BY created_at_epoch DESC, id ASC",
|
|
677
|
+
)
|
|
678
|
+
.all(status) as ActionQueueItemRow[];
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Clear all action queue items.
|
|
683
|
+
*/
|
|
684
|
+
export function clearActionQueueItems(): void {
|
|
685
|
+
const db = getMemoryDB();
|
|
686
|
+
db.run("DELETE FROM action_queue_items");
|
|
687
|
+
}
|
|
688
|
+
|
|
577
689
|
// ============================================================================
|
|
578
690
|
// FTS5 Maintenance
|
|
579
691
|
// ============================================================================
|