(deps: CtxNoteToolDeps)
| 162 | } |
| 163 | |
| 164 | function createCtxNoteTool(deps: CtxNoteToolDeps): ToolDefinition { |
| 165 | return tool({ |
| 166 | description: CTX_NOTE_DESCRIPTION, |
| 167 | args: { |
| 168 | action: tool.schema |
| 169 | .enum(["write", "read", "dismiss", "update"]) |
| 170 | .optional() |
| 171 | .describe( |
| 172 | "Operation to perform. Defaults to 'write' when content is provided, otherwise 'read'.", |
| 173 | ), |
| 174 | content: tool.schema |
| 175 | .string() |
| 176 | .optional() |
| 177 | .describe("Note text to store when action is 'write'."), |
| 178 | surface_condition: tool.schema |
| 179 | .string() |
| 180 | .optional() |
| 181 | .describe( |
| 182 | "Externally verifiable condition for smart notes. A separate background agent (dreamer) checks this using gh CLI, web fetches, file reads, git, etc. — NOT your conversation history. Use only for things like GitHub PR/issue state, release tags, file contents, or workflow runs. DO NOT use for 'when the user mentions X' / 'when we revisit Y' / 'when relevant to current task' — dreamer has no access to session context. For session-relative reminders, omit this and write a regular note.", |
| 183 | ), |
| 184 | filter: tool.schema |
| 185 | .enum(["all", "active", "pending", "ready", "dismissed"]) |
| 186 | .optional() |
| 187 | .describe( |
| 188 | "Optional read filter. Defaults to active session notes + ready smart notes. Use 'all' to inspect every status or 'pending' to inspect unsurfaced smart notes.", |
| 189 | ), |
| 190 | limit: tool.schema |
| 191 | .number() |
| 192 | .optional() |
| 193 | .describe("Max notes per section for read, newest first (default: 25)"), |
| 194 | offset: tool.schema |
| 195 | .number() |
| 196 | .optional() |
| 197 | .describe("Skip this many newest notes for read — page older ones (default: 0)"), |
| 198 | note_id: tool.schema |
| 199 | .number() |
| 200 | .optional() |
| 201 | .describe("Note ID (required for 'dismiss' and 'update' actions)."), |
| 202 | }, |
| 203 | async execute(args: CtxNoteArgs, toolContext) { |
| 204 | const sessionId = toolContext.sessionID; |
| 205 | // Infer write only on NON-EMPTY content. GPT-family models fill every |
| 206 | // optional param (content:"" for a read), so a bare `typeof === "string"` |
| 207 | // check would mis-infer `write` and then reject the empty content. |
| 208 | const action = args.action ?? (args.content?.trim() ? "write" : "read"); |
| 209 | |
| 210 | // Resolve the session's actual project from `toolContext.directory` |
| 211 | // each call. OpenCode's top-level `ctx.directory` (the launch dir) |
| 212 | // can differ from the session's working directory when the user |
| 213 | // runs `opencode -s <id>` from outside the project. |
| 214 | const projectIdentity = deps.resolveProjectPath?.(toolContext.directory); |
| 215 | |
| 216 | if (action === "write") { |
| 217 | const content = args.content?.trim(); |
| 218 | if (!content) { |
| 219 | return "Error: 'content' is required when action is 'write'."; |
| 220 | } |
| 221 |
no outgoing calls
no test coverage detected