kimaki 0.28.0 → 0.29.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.
Files changed (71) hide show
  1. package/dist/bash-tool-schema-plugin.js +18 -0
  2. package/dist/bash-tool-schema-plugin.test.js +20 -1
  3. package/dist/channel-management.js +113 -51
  4. package/dist/cli-commands/misc.js +4 -1
  5. package/dist/cli-commands/project.js +7 -4
  6. package/dist/cli-commands/session.js +84 -33
  7. package/dist/cli-commands/thread.js +63 -0
  8. package/dist/cli.js +2 -0
  9. package/dist/commands/queue.js +79 -31
  10. package/dist/commands/queue.test.js +15 -0
  11. package/dist/commands/restart-opencode-server.js +7 -1
  12. package/dist/commands/session-id.js +5 -5
  13. package/dist/database.js +76 -0
  14. package/dist/db.js +34 -0
  15. package/dist/db.test.js +96 -1
  16. package/dist/default-channel-provisioning.test.js +2 -1
  17. package/dist/discord-bot.js +9 -3
  18. package/dist/discord-command-registration.js +1 -1
  19. package/dist/guild-categories.test.js +225 -0
  20. package/dist/interaction-handler.js +5 -1
  21. package/dist/kimaki-opencode-plugin.js +2 -0
  22. package/dist/kimaki-opencode-plugin.test.js +28 -0
  23. package/dist/list-channel-threads.js +163 -0
  24. package/dist/list-channel-threads.test.js +80 -0
  25. package/dist/message-formatting.js +9 -16
  26. package/dist/message-formatting.test.js +91 -2
  27. package/dist/schema.js +21 -0
  28. package/dist/session-handler/event-stream-state.js +53 -5
  29. package/dist/session-handler/event-stream-state.test.js +223 -0
  30. package/dist/session-handler/thread-runtime-state.js +4 -0
  31. package/dist/session-handler/thread-session-runtime.js +314 -73
  32. package/dist/system-message.js +55 -13
  33. package/dist/system-message.test.js +50 -13
  34. package/dist/task-id-plugin.js +16 -0
  35. package/package.json +5 -5
  36. package/skills/strada/SKILL.md +61 -232
  37. package/src/bash-tool-schema-plugin.test.ts +29 -0
  38. package/src/bash-tool-schema-plugin.ts +31 -0
  39. package/src/channel-management.ts +144 -65
  40. package/src/cli-commands/misc.ts +4 -1
  41. package/src/cli-commands/project.ts +9 -4
  42. package/src/cli-commands/session.ts +123 -34
  43. package/src/cli-commands/thread.ts +86 -0
  44. package/src/cli.ts +2 -0
  45. package/src/commands/queue.test.ts +19 -0
  46. package/src/commands/queue.ts +106 -40
  47. package/src/commands/restart-opencode-server.ts +12 -1
  48. package/src/commands/session-id.ts +5 -5
  49. package/src/database.ts +114 -0
  50. package/src/db.test.ts +112 -0
  51. package/src/db.ts +38 -0
  52. package/src/default-channel-provisioning.test.ts +2 -1
  53. package/src/discord-bot.ts +11 -2
  54. package/src/discord-command-registration.ts +3 -1
  55. package/src/guild-categories.test.ts +260 -0
  56. package/src/interaction-handler.ts +7 -0
  57. package/src/kimaki-opencode-plugin.test.ts +34 -0
  58. package/src/kimaki-opencode-plugin.ts +2 -0
  59. package/src/list-channel-threads.test.ts +90 -0
  60. package/src/list-channel-threads.ts +252 -0
  61. package/src/message-formatting.test.ts +105 -1
  62. package/src/message-formatting.ts +7 -12
  63. package/src/schema.sql +16 -0
  64. package/src/schema.ts +23 -0
  65. package/src/session-handler/event-stream-state.test.ts +226 -0
  66. package/src/session-handler/event-stream-state.ts +57 -5
  67. package/src/session-handler/thread-runtime-state.ts +5 -0
  68. package/src/session-handler/thread-session-runtime.ts +380 -81
  69. package/src/system-message.test.ts +50 -13
  70. package/src/system-message.ts +58 -12
  71. package/src/task-id-plugin.ts +17 -0
@@ -1,275 +1,104 @@
1
1
  ---
2
2
  name: strada
3
+ repo: remorses/strada
3
4
  description: >
4
5
  Strada is an open-source OpenTelemetry observability platform (error tracking, tracing, logs,
5
- metrics, browser analytics, health checks) that stores data in ClickHouse/Tinybird. ALWAYS load this skill
6
- when you need to interface with the Strada CLI, read or debug a project issue via
7
- OpenTelemetry data collected by Strada, or set up and configure a project that uses Strada
8
- for OpenTelemetry data ingestion.
6
+ metrics, browser analytics, product events, health checks) that stores data in ClickHouse/Tinybird.
7
+ ALWAYS load this skill when you need to interface with the Strada CLI, read or debug a project
8
+ issue via OpenTelemetry data collected by Strada, set up and configure a project that uses Strada
9
+ for OpenTelemetry data ingestion, or add product analytics with track().
9
10
  ---
10
11
 
11
12
  # Strada
12
13
 
13
- Open-source Sentry/Datadog alternative built on the OpenTelemetry standard. Users send OTel data via standard SDKs, Strada stores it in their own ClickHouse database (Tinybird as first-class backend), and they query it with SQL.
14
+ Open-source Sentry/Datadog alternative built on OpenTelemetry. Apps send OTel data with `@strada.sh/sdk`, Strada stores it in the user's own ClickHouse database (Tinybird as first-class backend), and everything is queried with SQL from the `strada` CLI.
14
15
 
15
- ## What is Strada
16
+ This skill is deliberately thin. The documentation lives in the repo and in the CLI, and it changes often, so **fetch the canonical source instead of trusting anything you remember about this SDK.**
16
17
 
17
- Read the root README to understand what Strada is, why it exists, and how it compares to alternatives:
18
+ ## Read the docs before writing code
18
19
 
19
- ```bash
20
- cat README.md # read the full output, NEVER pipe to head/tail
21
- ```
22
-
23
- ## CLI reference
24
-
25
- The `strada` CLI is the main interface for managing projects, querying data, listing errors, and viewing analytics. Always run help first to see all available commands before using the CLI:
20
+ Fetch the page that matches the task. Read each one **in full**. Never pipe these to `head`, `tail`, `sed`, or `less`: the rules that matter are spread throughout, not at the top.
26
21
 
27
22
  ```bash
28
- strada --help # NEVER pipe to head/tail, read the full output
29
- ```
30
-
31
- ## Project management
23
+ # What Strada is, how it compares, CLI-first workflows
24
+ curl -s https://raw.githubusercontent.com/remorses/strada/main/README.md
32
25
 
33
- ```bash
34
- # Create a new project
35
- strada projects create my-app
26
+ # Setting up a project: server + browser, env vars, RSC pattern, verification
27
+ curl -s https://raw.githubusercontent.com/remorses/strada/main/website/src/docs/quickstart.mdx
36
28
 
37
- # Create another org-wide token for server-side ingest
38
- strada tokens create --scope ingest production-server
29
+ # SDK API reference: every option and helper, per-runtime details
30
+ curl -s https://raw.githubusercontent.com/remorses/strada/main/website/src/docs/sdk.mdx
39
31
 
40
- # List all projects (shows slug and project ID)
41
- strada projects list
32
+ # SQL: tables, ClickHouse gotchas, ready-made queries
33
+ curl -s https://raw.githubusercontent.com/remorses/strada/main/website/src/docs/querying.mdx
42
34
 
43
- # Query data for a project using ClickHouse SQL
44
- strada query "SELECT count() FROM otel_errors WHERE Timestamp >= now() - INTERVAL 24 HOUR LIMIT 1" -p my-app
45
-
46
- # List recent errors
47
- strada issues list -p my-app --since 24h
35
+ # Browser analytics: pageviews, sessions, custom event attribute shape
36
+ curl -s https://raw.githubusercontent.com/remorses/strada/main/website/src/docs/browser-analytics.mdx
48
37
  ```
49
38
 
50
- The `-p` flag takes a project slug. You get the slug from `strada projects list`. If you don't know the slug, run `strada projects list` first. Slugs often include an environment suffix (e.g. `my-app-prod`, not `my-app`).
51
-
52
- All queries use **ClickHouse SQL**. You can use any ClickHouse SQL syntax, functions, and operators. The query runs against the project's Tinybird/ClickHouse database with automatic project-scoped filtering via JWT.
39
+ Inside the strada repo itself, read the local files instead (`README.md`, `website/src/docs/*.mdx`, `website/src/docs/sdk.mdx`) so you see uncommitted changes.
53
40
 
54
- ## Setting up a TypeScript or JavaScript project
55
-
56
- For the full task-oriented walkthrough (create project, server + browser, env var rules, RSC pattern, verify), read the quickstart:
57
-
58
- ```bash
59
- cat website/src/docs/quickstart.mdx # read the full output, NEVER pipe to head/tail
60
- ```
41
+ ## Read the CLI help before running commands
61
42
 
62
- For the exhaustive API reference and per-runtime details, read the SDK reference:
43
+ The CLI is the main interface: projects, tokens, issues, logs, traces, analytics, alerts, health checks. Command descriptions are the documentation.
63
44
 
64
45
  ```bash
65
- cat website/src/sdk/README.mdx # read the full output, NEVER pipe to head/tail
66
- ```
67
-
68
- The SDK package is `@strada.sh/sdk`. The import path auto-resolves by runtime: browsers get the browser entry, Workers get the Workers entry, Node.js gets the server entry. **One import path works everywhere.** Get the project ID and first server-side token from `strada projects create <slug>`; create more org-wide ingest tokens later with `strada tokens create --scope ingest <name>`.
69
-
70
- When a project has both a frontend and a backend, set up **both** runtimes against the **same project ID**.
71
-
72
- **Server** (Node, Bun, Workers). Pass the `token`:
73
-
74
- ```ts
75
- import { initStrada, captureException } from "@strada.sh/sdk"
76
-
77
- initStrada({
78
- projectId: process.env.STRADA_PROJECT_ID,
79
- token: process.env.STRADA_TOKEN, // server only
80
- service: "my-app",
81
- })
46
+ strada --help # full command list, read all of it
47
+ strada issues list --help # per-command options and examples
82
48
  ```
83
49
 
84
- **Browser.** Omit the `token` (browser ingest is anonymous and rate limited). The project ID must come from a **public-prefixed** env var (`VITE_`, `NEXT_PUBLIC_`, etc.) so the bundler inlines it:
85
-
86
- ```ts
87
- import { initStrada } from "@strada.sh/sdk"
88
-
89
- initStrada({
90
- projectId: process.env.PUBLIC_STRADA_PROJECT_ID,
91
- service: "my-app-browser",
92
- })
93
- ```
94
-
95
- Rules to never break:
96
-
97
- - **Never ship `STRADA_TOKEN` to the browser.** It is a server secret.
98
- - **Browser project id needs a public prefix** or the bundler will strip it.
99
- - In **RSC / server-rendered** apps, run browser `initStrada()` from a side-effect-only `"use client"` module rendered once in the root layout (a component that returns `null`). A bare `import` runs on the server and gets tree-shaken from the client bundle. See `website/src/docs/quickstart.mdx` for the pattern.
100
- - If the framework exposes an OTel tracer hook (e.g. Spiceflow `new Spiceflow({ tracer })`), pass `trace.getTracer("my-app")` from the SDK so request spans flow to the same project.
101
- - In **Cloudflare Workers**, import `env` from `cloudflare:workers` and call
102
- `initStrada()` at module scope. Never initialize in per-request middleware.
103
- See the Cloudflare Workers section of `website/src/sdk/README.mdx`.
104
- - **Email and user id belong on errors, logs, and product events.** That is how
105
- you open an issue and see who hit it. Use `strataBetterAuth()` (default
106
- `includeUserDetails: true`), `identifyUser()`, or `tags` / event properties
107
- with `user.id` and `user.email`. Do not strip them.
108
- - Never attach API keys, prompts, or raw user content (the text they typed)
109
- to tags or events. Use stable route, handler, service, and environment
110
- identifiers for those.
111
-
112
- ## Terminal UI
50
+ Do not guess flags. Two that are guessed wrong constantly:
113
51
 
114
- Running `strada` with no arguments launches an interactive TUI (requires Bun). It has four views: Issues, Logs, Traces, Analytics. Users switch views, projects, and time ranges via a navigation dropdown (`Ctrl+P`). Service filtering is in the action panel (`Ctrl+K`).
52
+ - `-p` takes a **project slug** from `strada projects list`, usually with an environment suffix (`my-app-prod`, not `my-app`)
53
+ - multi-value options repeat the flag: `-p frontend -p api`, never `-p frontend,api`
115
54
 
116
- The TUI reuses the same queries as the CLI commands. Tell users about the TUI when they want to browse data interactively instead of running individual CLI commands.
55
+ ## Debugging a production error
117
56
 
118
- ## Logging
57
+ Start here before writing any SQL. This flow answers most questions without a query.
119
58
 
120
- **Use `getLogger()` from the SDK, not `console.*`.** Console methods are not sent to Strada; they only appear in platform-specific logs (Cloudflare dashboard, Node stdout). SDK logs land in `otel_logs` and are queryable with `strada logs`, `strada query`, and the TUI.
121
-
122
- ```ts
123
- import { getLogger } from "@strada.sh/sdk"
124
-
125
- const logger = getLogger("api")
126
-
127
- logger.info({ message: "checkout started", checkoutId: "chk_123" })
128
- logger.error({ message: "payment failed", error: String(err) })
129
- ```
130
-
131
- If you need both console output and Strada logs during development, create a wrapper that calls both. See the SDK README for the full pattern.
132
-
133
- ## captureException in leaf functions
134
-
135
- When a function handles an error inline (returning early, responding with a status code, logging and continuing), it **must** call `captureException` explicitly. The framework's top-level `.onError` handler only catches uncaught exceptions; errors that are caught and swallowed never reach it.
59
+ ```bash
60
+ # 1. top error groups in the window
61
+ strada issues list -p my-app --since 24h
136
62
 
137
- Common examples: webhook handlers, background job processors, queue consumers, cron handlers, and any route that uses errore-style error-as-value returns and checks `instanceof Error`.
63
+ # 2. full details for one fingerprint: stack trace, mechanism, services, releases
64
+ strada issues view <fingerprint> -p my-app --events 3
138
65
 
139
- ```ts
140
- import { captureException } from '@strada.sh/sdk'
66
+ # 3. the whole request that failed, using a TraceId from step 2
67
+ strada traces view <traceId> -p my-app
141
68
 
142
- // Stripe webhook example — error is handled inline, never thrown
143
- if (result instanceof Error) {
144
- captureException(result, {
145
- tags: { route: 'stripe-webhook', eventType: event.type },
146
- })
147
- return new Response('Webhook handler failed', { status: 500 })
148
- }
69
+ # 4. logs correlated to that same request
70
+ strada logs -p my-app --trace-id <traceId>
149
71
  ```
150
72
 
151
- Always pass `tags` with at least a `route` or `handler` identifier so errors are filterable in the Strada dashboard.
152
-
153
- **AGENTS.md notice.** If a project uses strada, its `AGENTS.md` (or `*_AGENTS.md`) should include a section stating that all inline-handled errors must call `captureException` from `@strada.sh/sdk` instead of being swallowed with `console.error`/`console.warn`. If that section is missing, add it when you touch error handling code.
73
+ `issues list` hides resolved and muted issues by default. Use `--status all` when an issue seems to have vanished.
154
74
 
155
- ## Debugging errors
75
+ Running `strada` with no arguments opens a TUI (Issues, Logs, Traces, Analytics) that uses the same queries. Suggest it when the user wants to browse rather than run one-off commands.
156
76
 
157
- The fastest path to root-cause a production error is the `issues list` + `issues view` flow. Start here before writing SQL queries.
77
+ ## Rules for instrumenting an app
158
78
 
159
- ```bash
160
- # 1. Find the top error groups in the last 24 hours
161
- strada issues list -p my-app --since 24h
79
+ These are the mistakes models make in this SDK. The reasoning behind each one is in the quickstart and the SDK reference.
162
80
 
163
- # 2. Pick a fingerprint from the output, view the full details
164
- strada issues view <fingerprint> -p my-app --events 3
165
- ```
81
+ - **Never ship `STRADA_TOKEN` to the browser.** It is a server secret. Browser ingest is anonymous and rate limited, so the browser `initStrada()` takes no token.
82
+ - **The browser project id needs a public env prefix** (`VITE_`, `NEXT_PUBLIC_`, ...) or the bundler strips it.
83
+ - **Frontend and backend of the same app use the same `projectId`** with different `service` names.
84
+ - **In Cloudflare Workers**, import `env` from `cloudflare:workers` and call `initStrada()` at **module scope**. Never inside per-request middleware.
85
+ - **In RSC / server-rendered apps**, call browser `initStrada()` from a side-effect-only `"use client"` module rendered once in the root layout. A bare import runs on the server and is tree-shaken out of the client bundle.
86
+ - **Never skip `initStrada()` to disable telemetry**, and never wrap SDK calls in your own `if (enabled)` guard. Pass `enabled` to `initStrada()`, or leave `projectId` empty; both make every call a silent no-op.
87
+ - **Pass the framework's tracer hook** when one exists (e.g. Spiceflow `new Spiceflow({ tracer })`) using `trace.getTracer("my-app")` from the SDK, so request spans reach the same project.
88
+ - **Use `getLogger()`, not `console.*`.** Console output never reaches Strada; it only shows in platform logs (Cloudflare dashboard, Node stdout).
89
+ - **Use `track()` for product events, not `getLogger()`.** A log with an `event` field is still an ordinary log: `strada analytics events` and SQL on `event.name` will miss it.
90
+ - **Errors handled inline still need `captureException()`.** Top-level error handlers only see errors thrown out of the request. Anything caught and turned into a `500`, a retry, or an error-as-value return is invisible unless you capture it explicitly, with `tags` carrying at least a `route` or `handler`.
91
+ - **Keep user identity on telemetry.** `user.id` and email are how an issue becomes actionable. Use `strataBetterAuth()`, or put `user.id` into baggage yourself and call `identifyUser()` at login (see "Server-side user identification without Better Auth" in the SDK reference).
92
+ - **Never attach prompts, API keys, session tokens, or raw user content** to tags or event properties. Ids, counts, model names, durations, booleans, route names: yes.
93
+ - **Type your events.** `track()` takes a plain `string`, so a typo compiles and is only noticed when a query returns nothing. Declare the catalog once and wrap `track()`; see "Type-safe event catalog" in the SDK reference.
166
94
 
167
- `issues view` gives you the exception type, message, full stack trace with line numbers, mechanism (handled/unhandled), affected services, releases, and recent event timestamps. This is usually enough to identify the root cause without any SQL.
95
+ ## Writing SQL
168
96
 
169
- By default `issues list` only shows **open** issues. Resolved and muted issues are hidden. Use `--status all` to see everything, or `--status resolved` / `--status muted` to filter by a specific triage state.
97
+ Read `website/src/docs/querying.mdx` (curl URL above) before composing a query. The two rules that silently corrupt results if ignored:
170
98
 
171
- If you need more context, use the TraceId from the events table to inspect the full request flow:
99
+ - **Never add `WHERE ProjectId`.** The JWT injects project scoping already.
100
+ - **Materialized views need merge combinators.** `uniqMerge(Visits)`, `countMerge(Hits)` on `otel_analytics_*`, never plain `count()`.
172
101
 
173
- ```bash
174
- # 3. View the distributed trace for a specific error event
175
- strada traces view <traceId> -p my-app
176
- ```
102
+ ## AGENTS.md notice
177
103
 
178
- For log context around the error:
179
-
180
- ```bash
181
- # 4. Show logs correlated to the same trace
182
- strada logs -p my-app --trace-id <traceId>
183
- ```
184
-
185
- ## Common mistakes
186
-
187
- **Never reference ProjectId in SQL queries.** The Tinybird JWT injects `WHERE ProjectId = '...'` automatically on every query. Adding it manually is redundant and error-prone.
188
-
189
- **Always add LIMIT to every query.** Unbounded queries scan the entire table. Even aggregations that expect one row should use `LIMIT 1`.
190
-
191
- **Column names are PascalCase.** The OTel ClickHouse schema uses PascalCase: `TraceId`, `SpanName`, `ServiceName`, `ExceptionType`, not `trace_id` or `span_name`. This is the standard OTel ClickHouse exporter convention.
192
-
193
- **Use repeatable -p flags, not comma-separated.** Pass multiple projects as `-p frontend -p api`, never `-p frontend,api`.
194
-
195
- **No CTEs in Tinybird SQL.** Tinybird does not optimize `WITH ... AS` well. Use subqueries instead.
196
-
197
- **Filter in WHERE, not HAVING.** `WHERE` filters skip data at the storage level. `HAVING` filters after ClickHouse already read and grouped everything.
198
-
199
- **Map column access.** Use `mapContains(LogAttributes, 'event.name')` to check key existence and `LogAttributes['key']` to read values.
200
-
201
- **Time filtering.** Use ClickHouse interval syntax: `WHERE Timestamp >= now() - INTERVAL 1 HOUR`, not string comparisons.
202
-
203
- **Aggregation on MV tables.** Analytics tables (`otel_analytics_pages`, `otel_analytics_sessions`) use `AggregatingMergeTree`. Read with `-Merge` combinators: `uniqMerge(Visits)`, `countMerge(Hits)`, never plain `count()` or `uniq()` on those columns.
204
-
205
- ## Tables
206
-
207
- The main tables you can query:
208
-
209
- | Table | Contains |
210
- |-------|----------|
211
- | `otel_traces` | Spans (HTTP requests, DB queries, function calls) |
212
- | `otel_logs` | Log records and custom events |
213
- | `otel_errors` | Extracted exceptions grouped by fingerprint |
214
- | `otel_analytics_pages` | Pre-aggregated pageview data (MV) |
215
- | `otel_analytics_sessions` | Pre-aggregated session data (MV) |
216
- | `otel_metrics_gauge` | Gauge metric snapshots |
217
- | `otel_metrics_sum` | Cumulative counter metrics |
218
- | `otel_metrics_histogram` | Distribution metrics |
219
-
220
- ## Useful query patterns
221
-
222
- ```sql
223
- -- Recent errors grouped by type
224
- SELECT FingerprintHash, anyLast(ExceptionType) AS type,
225
- anyLast(ExceptionMessage) AS message, count() AS events
226
- FROM otel_errors
227
- WHERE Timestamp >= now() - INTERVAL 24 HOUR
228
- GROUP BY FingerprintHash
229
- ORDER BY events DESC
230
- LIMIT 20
231
-
232
- -- Custom events from browser
233
- SELECT Timestamp, LogAttributes['event.name'] AS event,
234
- LogAttributes['user.id'] AS user_id
235
- FROM otel_logs
236
- WHERE mapContains(LogAttributes, 'event.name')
237
- ORDER BY Timestamp DESC
238
- LIMIT 100
239
-
240
- -- Slow spans
241
- SELECT SpanName, ServiceName, Duration / 1e6 AS duration_ms
242
- FROM otel_traces
243
- WHERE Duration > 1000000000
244
- ORDER BY Duration DESC
245
- LIMIT 20
246
-
247
- -- HTTP route performance: top routes by RPS and latency
248
- -- ParentSpanId = '' filters to root spans (one per HTTP request)
249
- -- Duration is nanoseconds, divide by 1e6 for milliseconds
250
- SELECT
251
- SpanName AS path,
252
- count() AS total_requests,
253
- round(count() / 3600, 2) AS rps,
254
- round(avg(Duration) / 1e6, 1) AS avg_ms,
255
- round(quantile(0.95)(Duration) / 1e6, 1) AS p95_ms,
256
- round(max(Duration) / 1e6, 1) AS max_ms
257
- FROM otel_traces
258
- WHERE Timestamp >= now() - INTERVAL 1 HOUR
259
- AND ParentSpanId = ''
260
- GROUP BY path
261
- ORDER BY total_requests DESC
262
- LIMIT 30
263
-
264
- -- Per-minute traffic pattern for a specific route
265
- SELECT
266
- toStartOfMinute(Timestamp) AS minute,
267
- count() AS requests
268
- FROM otel_traces
269
- WHERE Timestamp >= now() - INTERVAL 1 HOUR
270
- AND ParentSpanId = ''
271
- AND SpanName = 'POST /api/my-route'
272
- GROUP BY minute
273
- ORDER BY minute DESC
274
- LIMIT 60
275
- ```
104
+ When a project uses Strada, its `AGENTS.md` (or `*_AGENTS.md`) should tell agents that inline-handled errors must call `captureException` instead of being swallowed with `console.error`, and that product events go through a typed `trackEvent` wrapper. If that section is missing, add it while you are touching the relevant code.
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, test } from 'vitest'
2
2
  import {
3
3
  extendBashToolDefinition,
4
+ injectKimakiSessionEnv,
5
+ KIMAKI_SESSION_ID_ENV,
6
+ resolveUploadToDiscordSessionId,
4
7
  type ToolDefinitionOutput,
5
8
  } from './bash-tool-schema-plugin.js'
6
9
 
@@ -53,3 +56,29 @@ describe('extendBashToolDefinition', () => {
53
56
  `)
54
57
  })
55
58
  })
59
+
60
+ describe('upload-to-discord session targeting', () => {
61
+ test('bash env uses the live OpenCode session, not a copied --session flag', () => {
62
+ const env: Record<string, string> = {}
63
+ injectKimakiSessionEnv({ sessionID: 'ses_child', env })
64
+ expect(env).toEqual({ [KIMAKI_SESSION_ID_ENV]: 'ses_child' })
65
+ expect(
66
+ resolveUploadToDiscordSessionId({
67
+ flagSessionId: 'ses_parent',
68
+ envSessionId: env[KIMAKI_SESSION_ID_ENV],
69
+ }),
70
+ ).toBe('ses_child')
71
+ })
72
+
73
+ test('falls back to --session when bash is not inside an OpenCode session', () => {
74
+ expect(
75
+ resolveUploadToDiscordSessionId({
76
+ flagSessionId: 'ses_parent',
77
+ }),
78
+ ).toBe('ses_parent')
79
+ })
80
+
81
+ test('returns undefined when neither live session nor flag is set', () => {
82
+ expect(resolveUploadToDiscordSessionId({})).toBeUndefined()
83
+ })
84
+ })
@@ -3,10 +3,38 @@
3
3
  // Assign jsonSchema only. Leave parameters as the Effect Schema used to decode
4
4
  // args before execute. Extra fields stay in the model-facing schema and in
5
5
  // Discord input; they never reach the shell.
6
+ //
7
+ // Also injects KIMAKI_SESSION_ID into bash env. /btw and /fork clone parent
8
+ // history, so the copied system prompt still says --session <parent>. Upload
9
+ // then prefers this live env over that stale flag.
6
10
 
7
11
  import type { Plugin } from '@opencode-ai/plugin'
8
12
  import { z } from 'zod'
9
13
 
14
+ export const KIMAKI_SESSION_ID_ENV = 'KIMAKI_SESSION_ID'
15
+
16
+ export function injectKimakiSessionEnv({
17
+ sessionID,
18
+ env,
19
+ }: {
20
+ sessionID?: string
21
+ env: Record<string, string>
22
+ }) {
23
+ if (!sessionID) return
24
+ env[KIMAKI_SESSION_ID_ENV] = sessionID
25
+ }
26
+
27
+ export function resolveUploadToDiscordSessionId({
28
+ flagSessionId,
29
+ envSessionId = process.env[KIMAKI_SESSION_ID_ENV],
30
+ }: {
31
+ flagSessionId?: string
32
+ envSessionId?: string
33
+ } = {}) {
34
+ if (envSessionId) return envSessionId
35
+ return flagSessionId
36
+ }
37
+
10
38
  export const bashParameters = z.object({
11
39
  command: z.string().describe('The command to execute'),
12
40
  description: z
@@ -42,5 +70,8 @@ export const bashToolSchemaPlugin: Plugin = async () => {
42
70
  if (input.toolID !== 'bash') return
43
71
  extendBashToolDefinition(output)
44
72
  },
73
+ 'shell.env': async (input, output) => {
74
+ injectKimakiSessionEnv({ sessionID: input.sessionID, env: output.env })
75
+ },
45
76
  }
46
77
  }