@jeffober/thatch 0.1.41 → 0.1.43

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.
@@ -1,10 +1,11 @@
1
1
  # Watchers
2
2
 
3
- Watchers monitor external sources - today, GitHub pull requests and
4
- branches - and inject a notification into the conversation when
5
- something happens. You ask the agent to watch a PR or a branch; thatch
6
- polls it in the background; the agent gets woken up when a watched
7
- event occurs. No one has to keep asking "any updates yet?"
3
+ Watchers monitor external sources - today, GitHub pull requests,
4
+ branches, and local shell commands - and inject a notification into
5
+ the conversation when something happens. You ask the agent to watch a
6
+ PR, a branch, or a command; thatch polls it in the background; the
7
+ agent gets woken up when a watched event occurs. No one has to keep
8
+ asking "any updates yet?"
8
9
 
9
10
  ## What it does
10
11
 
@@ -34,18 +35,47 @@ Branch watched events:
34
35
  on the branch, optionally filtered by workflow name (substring,
35
36
  case-insensitive)
36
37
 
38
+ Command watched events:
39
+
40
+ - `command_success` - the watched command exited 0. The agent calls
41
+ `thatch_watch_command_create` with a shell command that works as a
42
+ **condition variable, not a data pipe**: the watcher re-runs it every
43
+ poll and reads only the exit code - the command's output is
44
+ discarded, never delivered. A command watch is one-shot by design:
45
+ the first exit 0 fires one notification, then the watcher cancels
46
+ itself. The command must be a fast, idempotent status check (a
47
+ marker-file test, a health endpoint curl, a `gh run list` query).
48
+ Blocking waits like `gh run watch` or `tail -f` are not refused -
49
+ the tool guidance tells the agent not to use them, and a blocking
50
+ command is killed at the per-run timeout each cycle and
51
+ treated as not-done-yet. The command runs once at registration as
52
+ validation: a command that already exits 0 is refused (the condition
53
+ is already met), as is a command-not-found.
54
+
37
55
  Notifications are delivered as synthetic parts: the model sees them,
38
56
  the transcript does not. A notification triggers a model turn, so the
39
57
  agent can act on the event even while you are away.
40
58
 
41
59
  ## Privacy model
42
60
 
43
- Notifications carry pointer data only: who commented, what happened,
44
- and a URL. Comment bodies, diff contents, and CI logs never enter the
45
- context from the notification itself. This is deliberate - GitHub
46
- comments are written by strangers, and untrusted text flowing into a
47
- model's context is a prompt-injection vector. The agent fetches details
48
- on demand with `gh` when it decides to act.
61
+ Notifications carry pointer data and machine status: who acted, what
62
+ happened, a URL, and - for CI - the check or workflow name with its
63
+ conclusion (success, failure). Comment bodies, diff contents, and CI
64
+ logs never enter the context from the notification itself. This is
65
+ deliberate - GitHub comments are written by strangers, and untrusted
66
+ text flowing into a model's context is a prompt-injection vector.
67
+ Machine status fields come from the GitHub API rather than
68
+ user-written text, so they are safe to deliver. The agent fetches
69
+ everything else on demand with `gh` when it decides to act.
70
+
71
+ Command watches follow the same rule with a stricter boundary: the
72
+ notification carries the exit code and duration only. The command's
73
+ stdout and stderr are discarded - command output can be external
74
+ content (a curl response, a build log), so it never rides a
75
+ notification. The agent re-runs the command or reads logs itself when
76
+ it acts. The command runs in the project directory as the local user;
77
+ each run is killed at a 30-second timeout
78
+ (`THATCH_WATCH_COMMAND_TIMEOUT_SECONDS` overrides it).
49
79
 
50
80
  ## How to use it
51
81
 
@@ -56,13 +86,21 @@ Ask for it in conversation:
56
86
 
57
87
  The handling policy lives in your instruction. State it when you ask -
58
88
  the notification turn reads your policy from the conversation and
59
- follows it. The notification says what happened and where, not what to
60
- do about it.
89
+ follows it. The notification says what happened and where, not what
90
+ to do about it.
91
+
92
+ For waiting on CI, pick the tool that fits the wait. A short bounded
93
+ wait (a couple of minutes) can simply be polled in the conversation.
94
+ Watchers are the better fit for longer or open-ended waits, and for
95
+ post-merge builds on main; the creation output states when the first
96
+ poll lands so the choice compares real numbers.
61
97
 
62
98
  Other tools:
63
99
 
64
100
  - `thatch_watch_branch_create` - watch a branch (typically main) for
65
101
  commits, CI check runs, and workflow runs
102
+ - `thatch_watch_command_create` - wait on any local condition via a
103
+ shell command that exits 0 when the wait is over
66
104
  - `thatch_watch_list` - shows the session's active watchers
67
105
  - `thatch_watch_cancel` - cancels one by id
68
106
 
@@ -72,7 +110,13 @@ Watchers are in-memory and process-scoped:
72
110
 
73
111
  - Ending the opencode process (or restarting it) drops all watchers.
74
112
  - A watcher expires after 8 hours by default.
75
- - Each session can hold up to 5 active watchers.
113
+ - Each session can hold up to 5 active watchers (all sources share
114
+ the budget).
115
+ - A one-shot watch cancels itself after its first event - a single
116
+ "tell me when this run finishes" request leaves nothing polling
117
+ afterwards. Standing watches (the default) keep going until they
118
+ are cancelled, expire, or the process ends. Command watches are
119
+ always one-shot.
76
120
 
77
121
  If opencode restarts while a watch is active, re-register it - the
78
122
  session's conversation survives, so the policy you stated is still
@@ -83,8 +127,9 @@ there.
83
127
  - **opencode only.** MCP hosts (Claude Code, Cursor) have no way for a
84
128
  plugin to start a conversation turn, so the watch tools are not
85
129
  available there.
86
- - **Requires the `gh` CLI** to be installed and authenticated. Watch
87
- creation reports a clear error if it is not.
130
+ - **Requires the `gh` CLI** to be installed and authenticated for PR
131
+ and branch watches. Watch creation reports a clear error if it is
132
+ not. Command watches need only `bash`, not `gh`.
88
133
  - **Polling, not webhooks.** Events appear on the next poll cycle
89
134
  (60 seconds by default), not instantly.
90
135
  - **No delivery during an active turn.** If the agent is mid-turn when
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffober/thatch",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@opencode-ai/plugin": "^1.17.13",
32
+ "@opencode-ai/sdk": "1.17.13",
32
33
  "@types/bun": "^1.3.14",
33
34
  "markdownlint-cli2": "^0.23.0",
34
35
  "typescript": "^5.7.0",
package/src/chat-names.ts CHANGED
@@ -7,14 +7,13 @@
7
7
  * Klingons, Discworld, Sandman, D&D, pulp detectives with software puns,
8
8
  * hackerspeak callsigns, AI puns, golden-age sci-fi, spoofed famous robots,
9
9
  * unnamed-cast extras). fnord generates its names with an LLM at runtime;
10
- * thatch bakes the pool in - static contents, no model call - while the
11
- * draw itself is random (see ChatStore.assign).
10
+ * thatch bakes the pool in - static contents, no model call.
12
11
  *
13
- * A session may still claim a custom name (chat_register's optional arg);
14
- * the pool is the default and keeps the directory colorful with zero
15
- * bikeshedding. Pool names are case-insensitively distinct from each other,
16
- * matching the directory's NOCASE uniqueness rule - so pool assignment can
17
- * never collide with another pool name, only with a custom claim.
12
+ * The pool is the fallback base for registrations with no usable session
13
+ * title (ChatStore.register slugifies a random pool name). Pool names are
14
+ * case-insensitively distinct from each other; slugify is lossy, but a
15
+ * shared base is harmless - the per-base counter still yields unique
16
+ * names.
18
17
  *
19
18
  * Names are deliberately short: they appear inside wake prompts and
20
19
  * chat_list output, so every character costs context.