opencode-team-memory 1.0.2 → 1.2.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ - Add `omo-resume` CLI — auto-restore team context on opencode launch
6
+ - Three modes: `--always`, `--ask` (default), `--never`
7
+ - Reads latest role state from `.omo/team-memory/`
8
+ - Generates continuation prompt and passes to `opencode run`
9
+
10
+ ## 1.1.0
11
+
12
+ - Add `formatContinuation()` — generates role-aware continuation prompt
13
+ - Add `experimental.chat.system.transform` hook — injects role context at session start
14
+ - Add Context-Mode compatible JSON metadata to compaction snapshots
15
+ - Sessions now auto-restore role context on restart
16
+
17
+ ## 1.0.2
18
+
19
+ - Fix `scripts/preflight.sh` inclusion in npm tarball
20
+
3
21
  ## 1.0.1
4
22
 
5
23
  - Add `scripts/preflight.sh` — automated installation check
package/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  Persistent role-based memory for OpenCode + Oh-My-OpenCode(OmO) Team Mode.
4
4
  Context survives across sessions, Team Mode runs, and compaction.
5
5
 
6
+ **v1.1.0**: Auto-role restoration on session start — Agent recognizes its role without manual reload.
7
+
6
8
  ## Problem
7
9
 
8
10
  OmO Team Mode members are ephemeral — they die when the run ends (default: 120 min).
@@ -24,6 +26,39 @@ Override with `OPENCODE_TEAM_MEMORY_DIR` env var.
24
26
 
25
27
  Compaction hook injects compact summaries automatically — long sessions keep context.
26
28
 
29
+ ## Auto-Role Restoration (v1.1.0)
30
+
31
+ When opencode starts, the plugin checks saved role memory and injects a continuation prompt into the system prompt. The Agent automatically:
32
+
33
+ 1. Recognizes its role (engineer / tester / designer / director)
34
+ 2. Reads handoff state (who passed work and where it goes next)
35
+ 3. Sees recent NG items and critical decisions
36
+ 4. Knows confirmed and excluded scope
37
+
38
+ **No manual `role_memory_load` required.** The session starts with full context.
39
+
40
+ Output looks like:
41
+
42
+ ```
43
+ ## Team Continuation (opencode-team-memory)
44
+
45
+ ### Your Role: engineer
46
+ ### Handoff → tester
47
+ ### Status: ng_count=2
48
+ ### Critical Context
49
+ use postgres; adopt JWT; add rate limit
50
+ ### Recent NG Items
51
+ login redirect broken
52
+ token expiry too short
53
+ ### Confirmed Scope
54
+ - auth module
55
+
56
+ ### Instructions
57
+ 1. role_memory_load(role="engineer") before starting
58
+ 2. Handoff target is 'tester' — prepare output accordingly
59
+ 3. Address NG items first
60
+ ```
61
+
27
62
  ## Enable / Disable
28
63
 
29
64
  Toggle without editing config files. Restart `opencode` after switching.
package/bin/omo-resume ADDED
@@ -0,0 +1,147 @@
1
+ #!/bin/sh
2
+ # omo-resume — 前回のチーム状態を復元して opencode を起動
3
+ # Usage: omo-resume [--always|--ask|--never]
4
+
5
+ set -eu
6
+
7
+ MEMORY_DIR="${OPENCODE_TEAM_MEMORY_DIR:-$(pwd)/.omo/team-memory}"
8
+ MODE="${OPENCODE_TEAM_CONTINUE:-ask}"
9
+ ROLES="engineer tester designer director"
10
+
11
+ # CLI引数でモード上書き
12
+ case "${1:-}" in
13
+ --always) MODE=always ;;
14
+ --ask) MODE=ask ;;
15
+ --never) MODE=never ;;
16
+ "") ;;
17
+ *) echo "Usage: omo-resume [--always|--ask|--never]" >&2; exit 1 ;;
18
+ esac
19
+
20
+ # never モード: 通常起動
21
+ if [ "$MODE" = "never" ]; then
22
+ echo "Team continuation disabled. Starting opencode normally." >&2
23
+ exec opencode
24
+ fi
25
+
26
+ # メモリ読み取り
27
+ latest_role=""
28
+ latest_time=""
29
+ found=0
30
+ for role in $ROLES; do
31
+ file="$MEMORY_DIR/$role/context.json"
32
+ [ -f "$file" ] || continue
33
+ found=1
34
+ last_updated=$(python3 -c "
35
+ import json,sys
36
+ try:
37
+ d=json.load(open('$file'))
38
+ print(d.get('last_updated',''))
39
+ except: pass
40
+ " 2>/dev/null)
41
+ [ -n "$last_updated" ] || continue
42
+ if [ -z "$latest_time" ] || [ "$last_updated" \> "$latest_time" ]; then
43
+ latest_time="$last_updated"
44
+ latest_role="$role"
45
+ fi
46
+ done
47
+
48
+ # メモリがない場合: 通常起動
49
+ if [ "$found" -eq 0 ] || [ -z "$latest_role" ]; then
50
+ echo "No previous team context found. Starting opencode normally." >&2
51
+ exec opencode
52
+ fi
53
+
54
+ # 復元プロンプト生成
55
+ prompt=$(python3 -c "
56
+ import json, sys
57
+
58
+ file = '$MEMORY_DIR/$latest_role/context.json'
59
+ with open(file) as f:
60
+ d = json.load(f)
61
+
62
+ decisions = '; '.join(d.get('previous_decisions', [])[-3:]) or 'none'
63
+ ng = d.get('ng_history', [])[-2:]
64
+ handoff = d.get('handoff_to', '')
65
+ confirmed = d.get('confirmed_scope', [])
66
+ excluded = d.get('excluded_scope', [])
67
+
68
+ lines = [
69
+ 'Resume team work as $latest_role.',
70
+ '',
71
+ '## Team Continuation (omo-resume)',
72
+ '',
73
+ '### Your Role: $latest_role',
74
+ ]
75
+
76
+ if handoff:
77
+ lines.append('### Handoff \u2192 ' + handoff)
78
+
79
+ lines.extend([
80
+ '### Status: ng_count=' + str(len(d.get('ng_history', []))),
81
+ '',
82
+ '### Critical Context',
83
+ decisions,
84
+ ])
85
+
86
+ if ng:
87
+ lines.append('')
88
+ lines.append('### Recent NG Items')
89
+ for item in ng:
90
+ lines.append('- ' + item)
91
+
92
+ if confirmed:
93
+ lines.append('')
94
+ lines.append('### Confirmed Scope')
95
+ for s in confirmed:
96
+ lines.append('- ' + s)
97
+
98
+ if excluded:
99
+ lines.append('')
100
+ lines.append('### Excluded (DO NOT TOUCH)')
101
+ for s in excluded:
102
+ lines.append('- ' + s)
103
+
104
+ lines.extend([
105
+ '',
106
+ '### Instructions',
107
+ '1. role_memory_load(role=\"$latest_role\") to restore full context',
108
+ ])
109
+
110
+ if handoff:
111
+ lines.append(\"2. Handoff target is '\" + handoff + \"'. Prepare accordingly.\")
112
+ if len(d.get('ng_history', [])) > 0:
113
+ lines.append('3. Address NG items before handoff.')
114
+ elif len(d.get('ng_history', [])) > 0:
115
+ lines.append('2. Address NG items first.')
116
+
117
+ lines.append('')
118
+ print('\n'.join(lines))
119
+ " 2>/dev/null)
120
+
121
+ if [ -z "$prompt" ]; then
122
+ echo "Failed to generate continuation prompt. Starting opencode normally." >&2
123
+ exec opencode
124
+ fi
125
+
126
+ # 復元プロンプトを表示
127
+ echo "" >&2
128
+ echo "========================================" >&2
129
+ echo " Team Continuation" >&2
130
+ echo "========================================" >&2
131
+ echo " Role: $latest_role" >&2
132
+ echo " Updated: $latest_time" >&2
133
+ echo " Mode: $MODE" >&2
134
+ echo "========================================" >&2
135
+ echo "" >&2
136
+
137
+ # ask モード: 確認
138
+ if [ "$MODE" = "ask" ]; then
139
+ printf "Resume with this context? [Y/n] " >&2
140
+ read -r answer
141
+ case "$answer" in
142
+ [Nn]*) echo "Starting fresh session." >&2; exec opencode ;;
143
+ esac
144
+ fi
145
+
146
+ # always / ask(確認済み): opencode を起動
147
+ exec opencode run "$prompt"
package/index.ts CHANGED
@@ -2,7 +2,7 @@ import { type Plugin, tool } from "@opencode-ai/plugin"
2
2
  import { readFile, writeFile, mkdir, access } from "node:fs/promises"
3
3
  import { join } from "node:path"
4
4
  import { ALL_ROLES, EMPTY_MEMORY, type MemoryEntry, type Role, type SaveInput } from "./types"
5
- import { merge, format, formatCompact, formatSaveResult } from "./memory"
5
+ import { merge, format, formatCompact, formatSaveResult, formatContinuation } from "./memory"
6
6
 
7
7
  function getMemoryBase(directory: string): string {
8
8
  return process.env.OPENCODE_TEAM_MEMORY_DIR || join(directory, ".omo", "team-memory")
@@ -95,13 +95,43 @@ export const TeamMemoryPlugin: Plugin = async ({ directory }) => {
95
95
  for (const role of ALL_ROLES) {
96
96
  try {
97
97
  const entry = await load(role)
98
- if (entry) {
99
- output.context.push(formatCompact(entry))
100
- }
98
+ if (!entry) continue
99
+
100
+ // Human-readable compact summary
101
+ output.context.push(formatCompact(entry))
102
+
103
+ // Context-Mode compatible: structured metadata for FTS5 indexing
104
+ output.context.push(
105
+ JSON.stringify({
106
+ source: "opencode-team-memory",
107
+ role: entry.role,
108
+ handoff_to: entry.handoff_to,
109
+ ng_count: entry.ng_history.length,
110
+ decisions: entry.previous_decisions.slice(-3),
111
+ last_updated: entry.last_updated,
112
+ })
113
+ )
101
114
  } catch {
102
115
  // skip roles with no saved memory
103
116
  }
104
117
  }
105
118
  },
119
+
120
+ "experimental.chat.system.transform": async (_input, output) => {
121
+ const blocks: string[] = []
122
+ for (const role of ALL_ROLES) {
123
+ try {
124
+ const entry = await load(role)
125
+ if (!entry) continue
126
+ const block = formatContinuation(entry, role)
127
+ if (block) blocks.push(block)
128
+ } catch {
129
+ // skip corrupted files
130
+ }
131
+ }
132
+ if (blocks.length > 0) {
133
+ output.context.push(blocks.join("\n\n"))
134
+ }
135
+ },
106
136
  }
107
137
  }
package/memory.ts CHANGED
@@ -97,3 +97,50 @@ export function formatSaveResult(entry: MemoryEntry): string {
97
97
  entry.handoff_to ? ` Next: → ${entry.handoff_to}` : "",
98
98
  ].join("\n")
99
99
  }
100
+
101
+ export function formatContinuation(entry: MemoryEntry | null, role: Role): string {
102
+ if (!entry) return ""
103
+
104
+ const decisions = entry.previous_decisions.slice(-3).join("; ")
105
+ const ng = entry.ng_history.slice(-2)
106
+
107
+ const lines: string[] = [
108
+ "## Team Continuation (opencode-team-memory)",
109
+ "",
110
+ `### Your Role: ${role}`,
111
+ ]
112
+
113
+ if (entry.handoff_to) {
114
+ lines.push(`### Handoff → ${entry.handoff_to}`)
115
+ }
116
+
117
+ lines.push(`### Status: ng_count=${entry.ng_history.length}`)
118
+
119
+ if (decisions) {
120
+ lines.push(`### Critical Context\n${decisions}`)
121
+ }
122
+
123
+ if (ng.length > 0) {
124
+ lines.push(`### Recent NG Items\n${ng.join("\n")}`)
125
+ }
126
+
127
+ if (entry.confirmed_scope.length > 0) {
128
+ lines.push(`### Confirmed Scope\n${entry.confirmed_scope.map(s => `- ${s}`).join("\n")}`)
129
+ }
130
+
131
+ if (entry.excluded_scope.length > 0) {
132
+ lines.push(`### Excluded (DO NOT TOUCH)\n${entry.excluded_scope.map(s => `- ${s}`).join("\n")}`)
133
+ }
134
+
135
+ lines.push(
136
+ "",
137
+ "### Instructions",
138
+ `1. role_memory_load(role="${role}") before starting`,
139
+ entry.handoff_to ? `2. Handoff target is '${entry.handoff_to}' — prepare output accordingly` : "",
140
+ entry.ng_history.length > 0 ? `${entry.handoff_to ? "3" : "2"}. Address NG items first` : "",
141
+ "0. Resume work based on context above",
142
+ ""
143
+ )
144
+
145
+ return lines.filter(Boolean).join("\n")
146
+ }
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "opencode-team-memory",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Persistent role-based memory for OpenCode/OmO Team Mode — survives across sessions and runs",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
+ "bin": {
8
+ "omo-resume": "bin/omo-resume"
9
+ },
7
10
  "files": [
8
11
  "index.ts",
9
12
  "types.ts",
10
13
  "memory.ts",
14
+ "bin/",
11
15
  "scripts/",
12
16
  "README.md",
13
17
  "LICENSE",
package/types.ts CHANGED
@@ -42,3 +42,14 @@ export interface SaveInput {
42
42
  handoff_to?: string
43
43
  raw?: string
44
44
  }
45
+
46
+ export interface ContinueState {
47
+ role: Role
48
+ handoff_to: string
49
+ last_action: string
50
+ ng_count: number
51
+ confirmed_scope: string[]
52
+ excluded_scope: string[]
53
+ decisions_summary: string
54
+ ng_summary: string
55
+ }