opencode-btw 0.1.0 → 0.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/README.md +34 -10
- package/package.json +6 -2
- package/src/core.ts +143 -0
- package/src/plugin.ts +102 -96
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Hint injection plugin for [OpenCode](https://opencode.ai) — nudge the model mid-task without interrupting its flow.
|
|
4
4
|
|
|
5
|
-
When the model is stuck in a loop or heading in the wrong direction, `/btw` lets you inject a hint into its context without sending a new message. The hint
|
|
5
|
+
When the model is stuck in a loop or heading in the wrong direction, `/btw` lets you inject a hint into its context without sending a new message. The hint is picked up on the next LLM call, including during tool loops.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -19,30 +19,54 @@ Restart OpenCode after adding the plugin.
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
/btw use the Edit tool instead of sed
|
|
23
|
-
/btw
|
|
24
|
-
/btw clear
|
|
25
|
-
/btw
|
|
22
|
+
/btw use the Edit tool instead of sed # add transient hint (auto-clears)
|
|
23
|
+
/btw pin always use pnpm, not npm # add persistent hint (manual clear)
|
|
24
|
+
/btw clear # remove all hints
|
|
25
|
+
/btw clear last # remove the most recently added hint
|
|
26
|
+
/btw # show all active hints
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
A confirmation message appears in the chat after each command. The model does not see this confirmation — only the
|
|
29
|
+
A confirmation message appears in the chat after each command. The model does not see this confirmation — only the hints themselves (via the system prompt).
|
|
30
|
+
|
|
31
|
+
### Stacking hints
|
|
32
|
+
|
|
33
|
+
Hints stack — each `/btw` adds to the list rather than replacing. This lets you layer corrections:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
/btw pin always use TypeScript # persistent base hint
|
|
37
|
+
/btw fix the bug in auth.ts first # transient nudge on top
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
After the model finishes its turn, the transient hint auto-clears while the pinned one remains.
|
|
41
|
+
|
|
42
|
+
### Transient vs. pinned hints
|
|
43
|
+
|
|
44
|
+
- **`/btw <hint>`** — auto-clears after the model finishes its current turn (including all tool calls). Use for one-off corrections and nudges.
|
|
45
|
+
- **`/btw pin <hint>`** — persists until you run `/btw clear`. Use for session-wide preferences like "always use pnpm" or "focus on the auth module".
|
|
29
46
|
|
|
30
47
|
## How it works
|
|
31
48
|
|
|
32
49
|
1. `/btw <hint>` saves the hint to a file on disk and cancels the command before an LLM call is made
|
|
33
|
-
2. On every subsequent LLM call, the `experimental.chat.system.transform` hook reads
|
|
34
|
-
3.
|
|
50
|
+
2. On every subsequent LLM call, the `experimental.chat.system.transform` hook reads all hints and appends them to the system prompt
|
|
51
|
+
3. When the model's turn finishes (`session.idle` event), transient hints are automatically removed while pinned hints stay
|
|
52
|
+
4. `/btw clear` removes all hints, `/btw clear last` removes the most recent one
|
|
35
53
|
|
|
36
|
-
Hints are **session-scoped** (each session has its own) and **project-scoped** (stored under a hash of the project directory). All data lives in `~/.cache/opencode/btw/`.
|
|
54
|
+
Hints are **session-scoped** (each session has its own) and **project-scoped** (stored under a hash of the project directory). All data lives in `~/.cache/opencode/btw/`. Hint files are cleaned up automatically when sessions are deleted.
|
|
37
55
|
|
|
38
56
|
## Use cases
|
|
39
57
|
|
|
40
58
|
- **Error loops**: the model keeps making the same mistake — `/btw you're using the wrong API, check the docs for v2`
|
|
41
|
-
- **Tool preference**: `/btw use Edit instead of sed, use Grep instead of grep`
|
|
59
|
+
- **Tool preference**: `/btw pin use Edit instead of sed, use Grep instead of grep`
|
|
42
60
|
- **Scope nudge**: `/btw focus only on the auth module, don't touch other files`
|
|
43
61
|
- **Strategy shift**: `/btw try a completely different approach, the current one won't work`
|
|
44
62
|
- **Direct questions**: `/btw what file are you currently editing?`
|
|
45
63
|
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bun test # run test suite
|
|
68
|
+
```
|
|
69
|
+
|
|
46
70
|
## License
|
|
47
71
|
|
|
48
72
|
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-btw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Hint injection plugin for OpenCode — nudge the model mid-task without interrupting its flow",
|
|
5
5
|
"main": "src/plugin.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "bun test"
|
|
8
|
+
},
|
|
6
9
|
"files": [
|
|
7
|
-
"src",
|
|
10
|
+
"src/plugin.ts",
|
|
11
|
+
"src/core.ts",
|
|
8
12
|
"LICENSE"
|
|
9
13
|
],
|
|
10
14
|
"keywords": [
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createHash } from "crypto"
|
|
2
|
+
import { mkdirSync, unlinkSync } from "fs"
|
|
3
|
+
|
|
4
|
+
export interface HintEntry {
|
|
5
|
+
text: string
|
|
6
|
+
pinned: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface HintFile {
|
|
10
|
+
hints: HintEntry[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ParsedCommand =
|
|
14
|
+
| { action: "clear"; which: "all" | "last" }
|
|
15
|
+
| { action: "status" }
|
|
16
|
+
| { action: "set"; text: string; pinned: boolean }
|
|
17
|
+
| { action: "error"; message: string }
|
|
18
|
+
|
|
19
|
+
export const BTW_HANDLED = "__BTW_HANDLED__"
|
|
20
|
+
|
|
21
|
+
export const BTW_SYSTEM_INSTRUCTIONS = `<btw-hint-system>
|
|
22
|
+
The user may inject real-time hints via the /btw command. These hints appear below as <btw-active-hint> blocks.
|
|
23
|
+
When a hint is present:
|
|
24
|
+
- Treat it as a direct, high-priority instruction from the user — equivalent to them telling you something face-to-face
|
|
25
|
+
- Apply it IMMEDIATELY to your current and future actions — do not wait for a "good moment"
|
|
26
|
+
- If the hint is a behavioral correction (e.g. "use Edit instead of sed"), adjust silently without calling attention to the change
|
|
27
|
+
- If the hint is a direct request or question (e.g. "explain what you're doing"), respond to it naturally
|
|
28
|
+
- If the hint contradicts your current approach, change course
|
|
29
|
+
- The hint persists until the user clears it — apply it to every action, not just the next one
|
|
30
|
+
- If the hint says to focus on specific files/areas, prioritize those and deprioritize others
|
|
31
|
+
</btw-hint-system>`
|
|
32
|
+
|
|
33
|
+
export function projectHash(directory: string): string {
|
|
34
|
+
return createHash("md5").update(directory).digest("hex").slice(0, 12)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function btwDir(directory: string): string {
|
|
38
|
+
return `${process.env.HOME}/.cache/opencode/btw/${projectHash(directory)}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function hintPath(dir: string, sessionID: string): string {
|
|
42
|
+
return `${dir}/${sessionID}.json`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ensureDir(dir: string): void {
|
|
46
|
+
try {
|
|
47
|
+
mkdirSync(dir, { recursive: true })
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function readHints(filePath: string): Promise<HintEntry[]> {
|
|
52
|
+
try {
|
|
53
|
+
const file = Bun.file(filePath)
|
|
54
|
+
if (await file.exists()) {
|
|
55
|
+
const data = await file.json()
|
|
56
|
+
// Handle new array format
|
|
57
|
+
if (Array.isArray(data?.hints) && data.hints.length > 0) {
|
|
58
|
+
return data.hints as HintEntry[]
|
|
59
|
+
}
|
|
60
|
+
// Handle legacy single-hint format
|
|
61
|
+
if (data?.text) {
|
|
62
|
+
return [{ text: data.text, pinned: data.pinned ?? false }]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
return []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function writeHints(
|
|
70
|
+
filePath: string,
|
|
71
|
+
hints: HintEntry[],
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
if (hints.length === 0) {
|
|
74
|
+
await clearHints(filePath)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
await Bun.write(filePath, JSON.stringify({ hints } satisfies HintFile))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function addHint(
|
|
81
|
+
filePath: string,
|
|
82
|
+
entry: HintEntry,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
const existing = await readHints(filePath)
|
|
85
|
+
existing.push(entry)
|
|
86
|
+
await writeHints(filePath, existing)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function clearHints(filePath: string): Promise<void> {
|
|
90
|
+
try {
|
|
91
|
+
unlinkSync(filePath)
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function removeTransient(filePath: string): Promise<boolean> {
|
|
96
|
+
const hints = await readHints(filePath)
|
|
97
|
+
const pinned = hints.filter((h) => h.pinned)
|
|
98
|
+
if (pinned.length === hints.length) return false // nothing removed
|
|
99
|
+
await writeHints(filePath, pinned)
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function removeLast(filePath: string): Promise<HintEntry | null> {
|
|
104
|
+
const hints = await readHints(filePath)
|
|
105
|
+
if (hints.length === 0) return null
|
|
106
|
+
const removed = hints.pop()!
|
|
107
|
+
await writeHints(filePath, hints)
|
|
108
|
+
return removed
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function parseCommand(rawArgs: string): ParsedCommand {
|
|
112
|
+
const args = rawArgs.trim()
|
|
113
|
+
|
|
114
|
+
if (args === "clear" || args === "reset") {
|
|
115
|
+
return { action: "clear", which: "all" }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (args === "clear last") {
|
|
119
|
+
return { action: "clear", which: "last" }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!args) {
|
|
123
|
+
return { action: "status" }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (args === "pin" || args.startsWith("pin ")) {
|
|
127
|
+
const text = args.slice(3).trim()
|
|
128
|
+
if (!text) {
|
|
129
|
+
return { action: "error", message: "Usage: /btw pin <hint>" }
|
|
130
|
+
}
|
|
131
|
+
return { action: "set", text, pinned: true }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { action: "set", text: args, pinned: false }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function buildSystemBlock(hints: HintEntry[]): string {
|
|
138
|
+
if (hints.length === 0) return ""
|
|
139
|
+
const hintBlocks = hints
|
|
140
|
+
.map((h) => `<btw-active-hint>\n${h.text}\n</btw-active-hint>`)
|
|
141
|
+
.join("\n\n")
|
|
142
|
+
return [BTW_SYSTEM_INSTRUCTIONS, "", hintBlocks].join("\n")
|
|
143
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -1,69 +1,39 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
|
-
|
|
3
|
-
import {
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BTW_HANDLED,
|
|
5
|
+
addHint,
|
|
6
|
+
buildSystemBlock,
|
|
7
|
+
btwDir,
|
|
8
|
+
clearHints,
|
|
9
|
+
ensureDir,
|
|
10
|
+
hintPath,
|
|
11
|
+
parseCommand,
|
|
12
|
+
readHints,
|
|
13
|
+
removeLast,
|
|
14
|
+
removeTransient,
|
|
15
|
+
} from "./core"
|
|
4
16
|
|
|
5
17
|
// btw — inject hints into the model's context without sending a new message.
|
|
6
18
|
//
|
|
7
|
-
// Uses
|
|
8
|
-
// 1.
|
|
9
|
-
//
|
|
10
|
-
// 2. experimental.chat.system.transform — appends
|
|
11
|
-
//
|
|
19
|
+
// Uses three hooks:
|
|
20
|
+
// 1. command.execute.before — intercepts /btw, saves hint, throws sentinel
|
|
21
|
+
// to cancel the LLM call entirely.
|
|
22
|
+
// 2. experimental.chat.system.transform — appends hints to the system prompt
|
|
23
|
+
// on every LLM call (including tool-loop iterations).
|
|
24
|
+
// 3. event — listens for session.idle to auto-clear transient hints, and
|
|
25
|
+
// session.deleted to clean up hint files.
|
|
12
26
|
//
|
|
13
27
|
// File layout:
|
|
14
28
|
// ~/.cache/opencode/btw/<project-hash>/
|
|
15
|
-
// <sessionID>.
|
|
16
|
-
|
|
17
|
-
// System prompt instructions — explains the btw system
|
|
18
|
-
const BTW_SYSTEM_INSTRUCTIONS = `<btw-hint-system>
|
|
19
|
-
The user may inject real-time hints via the /btw command. These hints appear below as <btw-active-hint> blocks.
|
|
20
|
-
When a hint is present:
|
|
21
|
-
- Treat it as a direct, high-priority instruction from the user — equivalent to them telling you something face-to-face
|
|
22
|
-
- Apply it IMMEDIATELY to your current and future actions — do not wait for a "good moment"
|
|
23
|
-
- If the hint is a behavioral correction (e.g. "use Edit instead of sed"), adjust silently without calling attention to the change
|
|
24
|
-
- If the hint is a direct request or question (e.g. "explain what you're doing"), respond to it naturally
|
|
25
|
-
- If the hint contradicts your current approach, change course
|
|
26
|
-
- The hint persists until the user clears it — apply it to every action, not just the next one
|
|
27
|
-
- If the hint says to focus on specific files/areas, prioritize those and deprioritize others
|
|
28
|
-
</btw-hint-system>`
|
|
29
|
-
|
|
30
|
-
function projectHash(directory: string): string {
|
|
31
|
-
return createHash("md5").update(directory).digest("hex").slice(0, 12)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function btwDir(directory: string): string {
|
|
35
|
-
return `${process.env.HOME}/.cache/opencode/btw/${projectHash(directory)}`
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Sentinel error — thrown to prevent OpenCode from calling prompt() after the
|
|
39
|
-
// command hook. Prevents an LLM call from being made for /btw commands.
|
|
40
|
-
const BTW_HANDLED = "__BTW_HANDLED__"
|
|
29
|
+
// <sessionID>.json # { hints: [{ text, pinned }] }
|
|
41
30
|
|
|
42
31
|
export const BtwPlugin: Plugin = async ({ directory, client }) => {
|
|
43
32
|
const dir = btwDir(directory)
|
|
33
|
+
ensureDir(dir)
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
mkdirSync(dir, { recursive: true })
|
|
47
|
-
} catch {}
|
|
48
|
-
|
|
49
|
-
const hintPath = (sessionID: string) => `${dir}/${sessionID}.txt`
|
|
35
|
+
const hint = (sessionID: string) => hintPath(dir, sessionID)
|
|
50
36
|
|
|
51
|
-
const readHint = async (sessionID: string): Promise<string> => {
|
|
52
|
-
try {
|
|
53
|
-
const file = Bun.file(hintPath(sessionID))
|
|
54
|
-
if (await file.exists()) {
|
|
55
|
-
return (await file.text()).trim()
|
|
56
|
-
}
|
|
57
|
-
} catch {}
|
|
58
|
-
return ""
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const writeHint = async (sessionID: string, hint: string) => {
|
|
62
|
-
await Bun.write(hintPath(sessionID), hint)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Send a no-op message that appears in the chat UI but is invisible to the
|
|
66
|
-
// LLM (noReply prevents inference, ignored: true hides it from message transforms).
|
|
67
37
|
const sendVisibleMessage = async (sessionID: string, text: string) => {
|
|
68
38
|
try {
|
|
69
39
|
await client.session.prompt({
|
|
@@ -73,73 +43,109 @@ export const BtwPlugin: Plugin = async ({ directory, client }) => {
|
|
|
73
43
|
parts: [{ type: "text" as const, text, ignored: true }],
|
|
74
44
|
},
|
|
75
45
|
})
|
|
76
|
-
} catch {
|
|
77
|
-
// Prompt API unavailable — silently skip
|
|
78
|
-
}
|
|
46
|
+
} catch {}
|
|
79
47
|
}
|
|
80
48
|
|
|
81
49
|
return {
|
|
82
|
-
// Register /btw as a command so it appears in /help and autocomplete.
|
|
83
|
-
// The template is minimal — command.execute.before intercepts before it's used.
|
|
84
50
|
config: (config) => {
|
|
85
51
|
;(config as any).command = (config as any).command ?? {}
|
|
86
52
|
;(config as any).command["btw"] = {
|
|
87
53
|
description:
|
|
88
|
-
"Inject a hint into the model's context (
|
|
54
|
+
"Inject a hint into the model's context (stacks; transient by default, use 'pin' to persist)",
|
|
89
55
|
template: "$ARGUMENTS",
|
|
90
56
|
}
|
|
91
57
|
},
|
|
92
58
|
|
|
93
|
-
|
|
59
|
+
event: async ({ event }) => {
|
|
60
|
+
// Auto-clear transient hints when the model finishes a complete turn
|
|
61
|
+
if (event.type === "session.idle") {
|
|
62
|
+
const sessionID = (event as any).properties?.sessionID
|
|
63
|
+
if (typeof sessionID !== "string") return
|
|
64
|
+
|
|
65
|
+
const removed = await removeTransient(hint(sessionID))
|
|
66
|
+
if (removed) {
|
|
67
|
+
await sendVisibleMessage(sessionID, "[btw] Transient hints auto-cleared")
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Clean up hint files when sessions are deleted
|
|
72
|
+
if (event.type === "session.deleted") {
|
|
73
|
+
const sessionID = (event as any).properties?.sessionID
|
|
74
|
+
if (typeof sessionID === "string") {
|
|
75
|
+
await clearHints(hint(sessionID))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
94
80
|
"command.execute.before": async (input, _output) => {
|
|
95
81
|
if (input.command !== "btw") return
|
|
96
82
|
|
|
97
|
-
const args = (input.arguments ?? "").trim()
|
|
98
83
|
const sessionID = input.sessionID
|
|
84
|
+
const parsed = parseCommand(input.arguments ?? "")
|
|
85
|
+
|
|
86
|
+
switch (parsed.action) {
|
|
87
|
+
case "clear":
|
|
88
|
+
if (parsed.which === "last") {
|
|
89
|
+
const removed = await removeLast(hint(sessionID))
|
|
90
|
+
if (removed) {
|
|
91
|
+
await sendVisibleMessage(
|
|
92
|
+
sessionID,
|
|
93
|
+
`[btw] Removed last hint: "${removed.text}"`,
|
|
94
|
+
)
|
|
95
|
+
} else {
|
|
96
|
+
await sendVisibleMessage(sessionID, "[btw] No hints to remove")
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
await clearHints(hint(sessionID))
|
|
100
|
+
await sendVisibleMessage(sessionID, "[btw] All hints cleared")
|
|
101
|
+
}
|
|
102
|
+
throw new Error(BTW_HANDLED)
|
|
103
|
+
|
|
104
|
+
case "status": {
|
|
105
|
+
const hints = await readHints(hint(sessionID))
|
|
106
|
+
if (hints.length === 0) {
|
|
107
|
+
await sendVisibleMessage(sessionID, "[btw] No hints set")
|
|
108
|
+
} else {
|
|
109
|
+
const lines = hints.map((h, i) => {
|
|
110
|
+
const label = h.pinned ? "pinned" : "transient"
|
|
111
|
+
return ` ${i + 1}. [${label}] "${h.text}"`
|
|
112
|
+
})
|
|
113
|
+
await sendVisibleMessage(
|
|
114
|
+
sessionID,
|
|
115
|
+
`[btw] Active hints:\n${lines.join("\n")}`,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
throw new Error(BTW_HANDLED)
|
|
119
|
+
}
|
|
99
120
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
case "error":
|
|
122
|
+
await sendVisibleMessage(sessionID, `[btw] ${parsed.message}`)
|
|
123
|
+
throw new Error(BTW_HANDLED)
|
|
124
|
+
|
|
125
|
+
case "set":
|
|
126
|
+
await addHint(hint(sessionID), {
|
|
127
|
+
text: parsed.text,
|
|
128
|
+
pinned: parsed.pinned,
|
|
129
|
+
})
|
|
130
|
+
const verb = parsed.pinned ? "Pinned hint" : "Hint"
|
|
131
|
+
await sendVisibleMessage(
|
|
132
|
+
sessionID,
|
|
133
|
+
`[btw] ${verb} added: "${parsed.text}"`,
|
|
134
|
+
)
|
|
135
|
+
throw new Error(BTW_HANDLED)
|
|
113
136
|
}
|
|
114
|
-
|
|
115
|
-
// Set the hint
|
|
116
|
-
await writeHint(sessionID, args)
|
|
117
|
-
await sendVisibleMessage(sessionID, `[btw] Hint set: "${args}"`)
|
|
118
|
-
throw new Error(BTW_HANDLED)
|
|
119
137
|
},
|
|
120
138
|
|
|
121
|
-
// Inject hint into system prompt on every LLM call (including tool loops).
|
|
122
139
|
"experimental.chat.system.transform": async (input, output) => {
|
|
123
140
|
const sessionID = (input as Record<string, unknown>)?.sessionID
|
|
124
141
|
if (typeof sessionID !== "string" || !sessionID) return
|
|
125
142
|
|
|
126
143
|
try {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
output.system.push(
|
|
131
|
-
[
|
|
132
|
-
BTW_SYSTEM_INSTRUCTIONS,
|
|
133
|
-
"",
|
|
134
|
-
"<btw-active-hint>",
|
|
135
|
-
hint,
|
|
136
|
-
"</btw-active-hint>",
|
|
137
|
-
].join("\n"),
|
|
138
|
-
)
|
|
144
|
+
const hints = await readHints(hint(sessionID))
|
|
145
|
+
if (hints.length > 0) {
|
|
146
|
+
output.system.push(buildSystemBlock(hints))
|
|
139
147
|
}
|
|
140
|
-
} catch {
|
|
141
|
-
// File read failed — no hint to inject
|
|
142
|
-
}
|
|
148
|
+
} catch {}
|
|
143
149
|
},
|
|
144
150
|
}
|
|
145
151
|
}
|