opencode-plannotator-tui 0.2.2 → 0.3.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - Select older completed replies with `/annotate 2`, `/annotate 3`, and so on.
6
+ Bare `/annotate` and `/annotate 1` still select the latest reply.
7
+ - Count only eligible completed text replies across history pages; report missing
8
+ replies and invalid numbers without opening a different source.
9
+ - `/annotate-last` now always selects the latest reply and ignores arguments.
10
+ **Migration:** replace `/annotate-last file.md` with `/annotate file.md`.
11
+ - Verify numbered feedback delivery and fixed latest-reply selection in the real
12
+ terminal smoke test, alongside 21 behavior tests.
13
+
3
14
  ## 0.2.2
4
15
 
5
16
  - All confirmation dialogs, status notices, errors, and the feedback header sent
package/README.md CHANGED
@@ -13,12 +13,14 @@ official OpenCode or Plannotator release.
13
13
 
14
14
  ```text
15
15
  /annotate Review the last completed assistant reply
16
+ /annotate 2 Review the second most recent reply
17
+ /annotate 3 Review the third most recent reply
16
18
  /annotate docs/plan.md Review a Markdown document
17
19
  /annotate "docs/release plan.md" Review a path containing spaces
18
20
  /annotate ~/Documents/notes.txt Review a plain-text document
19
21
  ```
20
22
 
21
- `/annotate-last` is an alias with the same optional file argument.
23
+ `/annotate-last` always reviews the latest completed reply; any supplied argument is ignored.
22
24
 
23
25
  ## Install
24
26
 
@@ -75,6 +77,9 @@ cancelled reviews submit nothing.
75
77
 
76
78
  **Replies:** the latest completed text reply, with paginated history lookup.
77
79
  Reasoning, in-progress messages and tool-call commentary are excluded.
80
+ Pass a positive integer to select the Nth most recent eligible reply: `1` is the
81
+ default, `2` is the previous reply, and so on. If there are too few replies, the
82
+ plugin reports that instead of opening another reply.
78
83
 
79
84
  **Documents:** local UTF-8 `.md`, `.mdx`, `.markdown` and `.txt` files up to 2 MiB.
80
85
  Relative paths resolve against the captured session's working directory, not the
@@ -160,8 +165,9 @@ See [release instructions](docs/releasing.md) and [changelog](CHANGELOG.md).
160
165
  这是 **Plannotator TUI 的非官方 OpenCode 适配插件**。
161
166
 
162
167
  - `/annotate`:批注上一条完整回复。
168
+ - `/annotate 2`:批注倒数第二条,`3`、`4` 依次往前;`1` 等同于默认。
163
169
  - `/annotate 文档.md`:批注文档,支持中文、空格和相对路径。
164
- - `/annotate-last`:同义入口,也可带文件路径。
170
+ - `/annotate-last`:固定批注最新一条完整回复,忽略附带参数。
165
171
  - 选中文字 → `c` 写批注 → Enter 保存 → `q` 返回 →「Send and continue」。
166
172
  - 文档采用独立快照,反馈附原文件路径,确认后发回发起批注的会话。
167
173
  - 更新:`opencode plugin update opencode-plannotator-tui@latest`,然后重新打开终端客户端。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plannotator-tui",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Unofficial OpenCode adapter for Plannotator TUI: annotate replies and documents without leaving your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,16 +13,42 @@
13
13
  "check": "node --check review.mjs && npm test && npm pack --dry-run",
14
14
  "prepublishOnly": "npm run check"
15
15
  },
16
- "files": ["tui.ts", "review.mjs", "README.md", "LICENSE", "CHANGELOG.md"],
17
- "engines": { "node": ">=22" },
18
- "peerDependencies": { "@opencode/plugin": ">=2.0.22 <3" },
19
- "peerDependenciesMeta": { "@opencode/plugin": { "optional": true } },
16
+ "files": [
17
+ "tui.ts",
18
+ "review.mjs",
19
+ "README.md",
20
+ "LICENSE",
21
+ "CHANGELOG.md"
22
+ ],
23
+ "engines": {
24
+ "node": ">=22"
25
+ },
26
+ "peerDependencies": {
27
+ "@opencode/plugin": ">=2.0.22 <3"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "@opencode/plugin": {
31
+ "optional": true
32
+ }
33
+ },
20
34
  "repository": {
21
35
  "type": "git",
22
36
  "url": "git+https://github.com/nxxxsooo/opencode-plannotator-tui.git"
23
37
  },
24
38
  "homepage": "https://github.com/nxxxsooo/opencode-plannotator-tui#readme",
25
- "bugs": { "url": "https://github.com/nxxxsooo/opencode-plannotator-tui/issues" },
26
- "keywords": ["opencode", "opencode-plugin", "plannotator", "tui", "annotation", "markdown", "review"],
27
- "publishConfig": { "access": "public" }
39
+ "bugs": {
40
+ "url": "https://github.com/nxxxsooo/opencode-plannotator-tui/issues"
41
+ },
42
+ "keywords": [
43
+ "opencode",
44
+ "opencode-plugin",
45
+ "plannotator",
46
+ "tui",
47
+ "annotation",
48
+ "markdown",
49
+ "review"
50
+ ],
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
28
54
  }
package/review.mjs CHANGED
@@ -48,6 +48,11 @@ async function documentSource(input, client, sessionID, signal) {
48
48
  }
49
49
 
50
50
  export function selectReply(messages) {
51
+ return eligibleReplies(messages)[0]
52
+ }
53
+
54
+ function eligibleReplies(messages) {
55
+ const replies = []
51
56
  const newest = [...messages].sort((a, b) =>
52
57
  b.time.created - a.time.created || b.id.localeCompare(a.id),
53
58
  )
@@ -55,12 +60,14 @@ export function selectReply(messages) {
55
60
  if (message.type !== "assistant" || message.time.completed === undefined || message.error) continue
56
61
  if (["tool-calls", "error", "content-filter"].includes(message.finish)) continue
57
62
  const text = message.content.filter(part => part.type === "text").map(part => part.text).join("\n\n")
58
- if (text.trim()) return { id: message.id, text }
63
+ if (text.trim()) replies.push({ id: message.id, text })
59
64
  }
65
+ return replies
60
66
  }
61
67
 
62
- async function latestReply(client, sessionID, signal) {
68
+ async function latestReply(client, sessionID, signal, position = 1) {
63
69
  const seen = new Set()
70
+ const messages = new Set()
64
71
  let cursor
65
72
  do {
66
73
  signal.throwIfAborted()
@@ -68,8 +75,11 @@ async function latestReply(client, sessionID, signal) {
68
75
  sessionID, type: "assistant", limit: 50,
69
76
  ...(cursor ? { cursor } : { order: "desc" }),
70
77
  }, { signal })
71
- const reply = selectReply(page.data)
72
- if (reply) return reply
78
+ for (const reply of eligibleReplies(page.data)) {
79
+ if (messages.has(reply.id)) continue
80
+ messages.add(reply.id)
81
+ if (--position === 0) return reply
82
+ }
73
83
  cursor = page.cursor?.next
74
84
  if (cursor && seen.has(cursor)) throw new Error("Session history returned a repeated page cursor.")
75
85
  seen.add(cursor)
@@ -125,11 +135,18 @@ export function createReviewCommand(context, options = {}) {
125
135
  // The UI cache contains only a recent page; a long tool run can push the last
126
136
  // completed reply out of it. Read the session-scoped history, newest first.
127
137
  const target = input.trim()
128
- const source = target
138
+ const numeric = target !== "" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(target)
139
+ const position = numeric ? Number(target) : 1
140
+ if (numeric && (!/^\d+$/.test(target) || !Number.isSafeInteger(position) || position < 1)) {
141
+ throw new Error("Choose a positive integer reply number (1 = latest, 2 = previous).")
142
+ }
143
+ const source = target && !numeric
129
144
  ? await documentSource(target, context.client, sessionID, controller.signal)
130
- : await latestReply(context.client, sessionID, controller.signal)
145
+ : await latestReply(context.client, sessionID, controller.signal, position)
131
146
  if (controller.signal.aborted) return
132
- if (!source) return notice("This session has no completed assistant reply yet.")
147
+ if (!source) return notice(position === 1
148
+ ? "This session has no completed assistant reply yet."
149
+ : `Can't find reply ${position}: this session has fewer completed assistant text replies.`)
133
150
 
134
151
  directory = await mkdtemp(path.join(options.tempRoot ?? os.tmpdir(), "opencode-annotate-"))
135
152
  keepDraft = true
package/tui.ts CHANGED
@@ -14,11 +14,21 @@ export default Plugin.define({
14
14
  commands: [{
15
15
  id: "opencode-plannotator-tui.annotate",
16
16
  title: "Annotate reply or document (TUI)",
17
- description: "Annotate the last reply or a file; press q, then confirm to send",
17
+ description: "Annotate a reply (1 = latest, 2 = previous) or a file; press q, then confirm to send",
18
18
  group: "Plannotator",
19
19
  palette: true,
20
- slash: { name: "annotate", aliases: ["annotate-last"], arguments: true },
20
+ slash: { name: "annotate", arguments: true },
21
21
  run: review.run,
22
+ }, {
23
+ id: "opencode-plannotator-tui.annotate-last",
24
+ title: "Annotate last reply (TUI)",
25
+ description: "Annotate the latest completed reply; press q, then confirm to send",
26
+ group: "Plannotator",
27
+ palette: true,
28
+ // Keep slash submission with a trailing space local to the command.
29
+ // Any supplied argument is deliberately ignored: this always means latest.
30
+ slash: { name: "annotate-last", arguments: true },
31
+ run: () => review.run(),
22
32
  }],
23
33
  }))
24
34
  return null