opencode-wake-plugin 0.1.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 +31 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/dist/contract.d.ts +13 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +39 -0
- package/dist/contract.js.map +1 -0
- package/dist/event-filter.d.ts +11 -0
- package/dist/event-filter.d.ts.map +1 -0
- package/dist/event-filter.js +43 -0
- package/dist/event-filter.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +58 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +157 -0
- package/dist/logger.js.map +1 -0
- package/dist/omo-detector.d.ts +14 -0
- package/dist/omo-detector.d.ts.map +1 -0
- package/dist/omo-detector.js +74 -0
- package/dist/omo-detector.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +126 -0
- package/dist/server.js.map +1 -0
- package/dist/tui.d.ts +9 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +32 -0
- package/dist/tui.js.map +1 -0
- package/dist/wake-coalescer.d.ts +15 -0
- package/dist/wake-coalescer.d.ts.map +1 -0
- package/dist/wake-coalescer.js +43 -0
- package/dist/wake-coalescer.js.map +1 -0
- package/dist/wake-dispatcher.d.ts +68 -0
- package/dist/wake-dispatcher.d.ts.map +1 -0
- package/dist/wake-dispatcher.js +126 -0
- package/dist/wake-dispatcher.js.map +1 -0
- package/package.json +73 -0
- package/src/contract.ts +48 -0
- package/src/event-filter.ts +57 -0
- package/src/index.ts +25 -0
- package/src/logger.ts +205 -0
- package/src/omo-detector.ts +82 -0
- package/src/server.ts +142 -0
- package/src/tui.ts +37 -0
- package/src/wake-coalescer.ts +54 -0
- package/src/wake-dispatcher.ts +167 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-07-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Defense-in-depth wake dispatcher that listens to opencode `session.idle` events and dispatches a fallback `client.session.promptAsync` when OMO's `parentWakePendingQueue` misses a wake.
|
|
9
|
+
- Per-parent 5500 ms coalesce window that sits just above OMO's 5000 ms `MIN_IDLE_TIME_MS`, so the plugin only fires when OMO has had its chance.
|
|
10
|
+
- Retry with exponential backoff (1 s / 3 s / 9 s) for transient `promptAsync` failures.
|
|
11
|
+
- Status-aware skip: when the parent session is `busy`/`running`/`retry`, the plugin does not double-wake.
|
|
12
|
+
- SDK-shape fallback for cross-version safety (legacy flat shape plus path/body/query shape).
|
|
13
|
+
- **Parent model preservation**: reads the parent's most recent assistant message and forwards its `{providerID, modelID}` through `promptAsync.body.model` so the resumed turn stays on the same provider.
|
|
14
|
+
- Structured logger with `OPENCODE_WAKE_PLUGIN_LOG` (level) and `OPENCODE_WAKE_PLUGIN_LOG_FORMAT` (pretty/json), routed through `client.app.log` so it never bleeds into the opencode editor.
|
|
15
|
+
- Self-disable on OMO `>= 5.0.0` (hypothetical "fully fixed" release).
|
|
16
|
+
- No-op TUI module so the plugin enumerates in opencode's plugins panel.
|
|
17
|
+
- 62 Vitest cases across 6 test files (model preservation, retry, status skip, give-up, contract shape, OMO detector, coalescer, logger).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- None (initial release).
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
- None.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- None.
|
|
27
|
+
|
|
28
|
+
### Security
|
|
29
|
+
- None known.
|
|
30
|
+
|
|
31
|
+
[0.1.0]: https://github.com/desendoo/opencode-wake-plugin/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 opencode-wake-plugin contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# opencode-wake-plugin
|
|
2
|
+
|
|
3
|
+
Defense-in-depth plugin for the [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent) deferred-wake cascade. When a parent session's wake is stuck in `parentWakePendingQueue` (a known OMO bug), this plugin listens to `session.idle` events directly through the opencode plugin SDK and dispatches a fallback wake. The fallback preserves the parent's last-used model so the resumed turn stays on the same provider.
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://opencode.ai/docs/plugins/)
|
|
7
|
+
[](https://github.com/code-yeongyu/oh-my-openagent/issues/5569)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
`oh-my-openagent` has a deferred-wake cascade bug ([issue #5569](https://github.com/code-yeongyu/oh-my-openagent/issues/5569), [#5790](https://github.com/code-yeongyu/oh-my-openagent/issues/5790)). PR [#5601](https://github.com/code-yeongyu/oh-my-openagent/pull/5601) shipped a partial fix (stranded-wake retry) in OMO 4.13.0+, but the 7-deferral-guard pattern still exists and your OMO 4.x may miss wakes in rare timing windows.
|
|
13
|
+
|
|
14
|
+
This plugin does **not** modify OMO. It listens to opencode `session.idle` events, coalesces them per parent, looks up parentage via `client.session.get()`, and dispatches a fallback `client.session.promptAsync` with retry/backoff. Before dispatch, it reads the parent's latest assistant message and forwards the same `{providerID, modelID}` so the resumed turn stays on the same provider.
|
|
15
|
+
|
|
16
|
+
The plugin self-disables cleanly when OMO reaches a "fully fixed" `5.0.0` release.
|
|
17
|
+
|
|
18
|
+
## Quick install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Clone the plugin
|
|
22
|
+
git clone https://github.com/desendoo/opencode-wake-plugin.git \
|
|
23
|
+
~/.config/opencode/plugins/opencode-wake-plugin
|
|
24
|
+
|
|
25
|
+
# 2. Install dependencies and build
|
|
26
|
+
cd ~/.config/opencode/plugins/opencode-wake-plugin
|
|
27
|
+
bun install
|
|
28
|
+
bun run build
|
|
29
|
+
|
|
30
|
+
# 3. Add the plugin path to opencode's plugin list
|
|
31
|
+
python3 - <<'PY'
|
|
32
|
+
import json, os
|
|
33
|
+
home = os.environ['HOME']
|
|
34
|
+
target = f'{home}/.config/opencode/plugins/opencode-wake-plugin'
|
|
35
|
+
for src in [f'{home}/.config/opencode/tui.json',
|
|
36
|
+
f'{home}/.config/opencode/opencode.json']:
|
|
37
|
+
if not os.path.isfile(src):
|
|
38
|
+
with open(src, 'w') as f:
|
|
39
|
+
json.dump({'$schema': 'https://opencode.ai/config.json',
|
|
40
|
+
'plugin': [target]}, f, indent=2)
|
|
41
|
+
continue
|
|
42
|
+
with open(src) as f: cfg = json.load(f)
|
|
43
|
+
plugins = cfg.setdefault('plugin', [])
|
|
44
|
+
if target not in plugins:
|
|
45
|
+
plugins.append(target)
|
|
46
|
+
with open(f'{src}.tmp', 'w') as f: json.dump(cfg, f, indent=2)
|
|
47
|
+
os.replace(f'{src}.tmp', src)
|
|
48
|
+
PY
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Restart OpenCode. You should see this log line on startup:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
[wake-plugin] wake-enforcer ACTIVE. Coalescing session.idle events with 5500ms fallback delay, backoff 1000/3000/9000ms.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That is the entire install. If you already have an `opencode.json` or `tui.json`, the snippet above appends to the existing `plugin` array instead of overwriting it. The plugin loader reads both files; TUI mode reads `tui.json` first.
|
|
58
|
+
|
|
59
|
+
## Verify
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd ~/.config/opencode/plugins/opencode-wake-plugin
|
|
63
|
+
bun run typecheck # tsc --noEmit, 0 errors expected
|
|
64
|
+
bun run test # 62 vitest cases should all pass
|
|
65
|
+
bun run build # generates dist/
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then dispatch a background task from a non-default model. The parent should resume on the same provider/model instead of reverting to the agent default.
|
|
69
|
+
|
|
70
|
+
## How it works
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
opencode server
|
|
74
|
+
└── Session goes idle
|
|
75
|
+
└── emits `session.idle` event
|
|
76
|
+
└── plugin's `event` hook fires
|
|
77
|
+
└── shouldWakeOn() — filter guard
|
|
78
|
+
└── lookupParentSessionID() — fetch session via client.session.get()
|
|
79
|
+
└── WakeCoalescer.queue(parent, child) ─ 5500ms fallback delay
|
|
80
|
+
└── (debounce elapses, batched)
|
|
81
|
+
└── WakeDispatcher.dispatch(parent, [children])
|
|
82
|
+
└── resolveParentModel() — read latest assistant message
|
|
83
|
+
└── client.session.promptAsync() ─ 1s/3s/9s backoff
|
|
84
|
+
└── parent LLM continues on the same model
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Key design choices:
|
|
88
|
+
|
|
89
|
+
- **5500 ms coalesce window** sits just above OMO's 5000 ms `MIN_IDLE_TIME_MS`, so the plugin only fires when OMO has had its chance and missed.
|
|
90
|
+
- **Parent model preservation** resolves the parent's most recent assistant message and sets `body.model` so the resumed turn uses the same `{providerID, modelID}` instead of the agent default.
|
|
91
|
+
- **Status-aware skip**: if the parent session is already `busy`/`running`/`retry`, the plugin skips the fallback wake to avoid duplicate prompts.
|
|
92
|
+
- **SDK-shape fallback**: the dispatcher retries the same prompt through both the legacy flat `promptAsync` shape and the current path/body/query shape for cross-SDK-version safety.
|
|
93
|
+
- **Structured logging** through `client.app.log` with `OPENCODE_WAKE_PLUGIN_LOG=debug|info|warn|error|silent` and `OPENCODE_WAKE_PLUGIN_LOG_FORMAT=json` for machine-readable traces.
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
The plugin runs with zero configuration. Optional environment variables:
|
|
98
|
+
|
|
99
|
+
| Variable | Default | Purpose |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `OPENCODE_WAKE_PLUGIN_LOG` | `info` | Log level: `debug`, `info`, `warn`, `error`, `silent` |
|
|
102
|
+
| `OPENCODE_WAKE_PLUGIN_LOG_FORMAT` | `pretty` | Log format: `pretty` (ISO + `[opencode-wake-plugin]` prefix) or `json` |
|
|
103
|
+
|
|
104
|
+
Set them before launching opencode:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
OPENCODE_WAKE_PLUGIN_LOG=debug opencode
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For JSON traces suitable for `jq` pipelines:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
OPENCODE_WAKE_PLUGIN_LOG_FORMAT=json opencode 2>&1 | jq 'select(.msg == "wake fired")'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
When OMO reaches a fully-fixed release (`>= 5.0.0`), the plugin returns `{}` from the entry point and self-disables with no further action.
|
|
117
|
+
|
|
118
|
+
## Module map
|
|
119
|
+
|
|
120
|
+
| File | Purpose |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `src/server.ts` | Plugin entry: wires hooks, coalescer, dispatcher, OMO probe |
|
|
123
|
+
| `src/event-filter.ts` | `shouldWakeOn(event)`: gates `session.idle` events |
|
|
124
|
+
| `src/wake-coalescer.ts` | `WakeCoalescer`: 5500 ms per-parent fallback delay |
|
|
125
|
+
| `src/wake-dispatcher.ts` | `WakeDispatcher`: promptAsync with 1 s/3 s/9 s retry |
|
|
126
|
+
| `src/omo-detector.ts` | `classifyOmoVersion()`: `>= 5.0.0` self-disable |
|
|
127
|
+
| `src/contract.ts` | `buildWakeMessage()`: chat.message payload builder |
|
|
128
|
+
| `src/logger.ts` | `StructuredLogger`: levels + JSON + `client.app.log` transport |
|
|
129
|
+
| `src/tui.ts` | No-op TUI module so the plugin enumerates in the opencode plugins panel |
|
|
130
|
+
| `src/index.ts` | V1 plugin module: `default = { id, server }` |
|
|
131
|
+
|
|
132
|
+
## Troubleshooting
|
|
133
|
+
|
|
134
|
+
### Plugin does not appear in the opencode plugins panel
|
|
135
|
+
|
|
136
|
+
- The plugin entry must export `default = { id, server }` (V1 shape), not the bare function. This repo already does so; if you forked it, check `src/index.ts`.
|
|
137
|
+
- Make sure both `opencode.json` **and** `tui.json` list the plugin path. TUI mode reads `tui.json` first; non-TUI modes read `opencode.json`.
|
|
138
|
+
- The plugin path in your config must be an **absolute path** — relative paths are rejected by the loader.
|
|
139
|
+
|
|
140
|
+
### Plugin loads but wake messages never fire
|
|
141
|
+
|
|
142
|
+
- Set `OPENCODE_WAKE_PLUGIN_LOG=debug` and dispatch a known background task. Look for `[opencode-wake-plugin] DEBUG session.idle received` events.
|
|
143
|
+
- If you see no events, the plugin may not be running — confirm with `opencode --print-logs | grep wake-plugin`.
|
|
144
|
+
|
|
145
|
+
### Parent resumes on the wrong model
|
|
146
|
+
|
|
147
|
+
- After dispatch, check `OPENCODE_WAKE_PLUGIN_LOG_FORMAT=json opencode 2>&1 | jq` for the `wake fired` event and confirm `body.model` is set.
|
|
148
|
+
- If `body.model` is missing on every wake, your parent's assistant history may be empty (very first turn). The plugin omits the field when no complete assistant message exists.
|
|
149
|
+
- The plugin does **not** preserve `variant` — the SDK 1.16.2 `promptAsync.body` shape does not include one. `mode` data is read from message history but not forwarded; only `model` is restored.
|
|
150
|
+
|
|
151
|
+
### Tests fail after a `bun install`
|
|
152
|
+
|
|
153
|
+
- Run `bun install` from the plugin directory. The package's `engines.node` requires Node 22+.
|
|
154
|
+
- The peer dependency `@opencode-ai/plugin` must resolve to `^1.16.0` (installed 1.16.2 in dev). If you see a peer-dependency warning, install with `bun install --force`.
|
|
155
|
+
|
|
156
|
+
## Limitations
|
|
157
|
+
|
|
158
|
+
- **OMO version probe is best-effort.** When OMO is loaded as `oh-my-openagent@latest` (the default), the version string is unknown and the plugin defaults to "active". A future release can read OMO's `package.json` directly from the install path.
|
|
159
|
+
- **`variant` is not preserved.** Installed SDK 1.16.2's `SessionPromptAsyncData.body` accepts `model`, `agent`, and `tools` but not `variant`. The plugin restores provider and model but does not attempt to forward `mode`.
|
|
160
|
+
- **No cross-provider crash detection.** The plugin does not validate that the resumed turn's provider/model matches what the rest of the session is using. Switching providers across a fallback wake is your responsibility to avoid.
|
|
161
|
+
- **Wake message is unilingual (English)**. Localize by editing `src/contract.ts` if you need other languages; the message is intentionally compact to minimize token cost.
|
|
162
|
+
|
|
163
|
+
## Uninstall
|
|
164
|
+
|
|
165
|
+
Remove the plugin entries from both config files and delete the directory:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
python3 - <<'PY'
|
|
169
|
+
import json, os
|
|
170
|
+
home = os.environ['HOME']
|
|
171
|
+
configs = [f'{home}/.config/opencode/opencode.json',
|
|
172
|
+
f'{home}/.config/opencode/tui.json']
|
|
173
|
+
target = f'{home}/.config/opencode/plugins/opencode-wake-plugin'
|
|
174
|
+
for src in configs:
|
|
175
|
+
if not os.path.isfile(src): continue
|
|
176
|
+
with open(src) as f: cfg = json.load(f)
|
|
177
|
+
cfg['plugin'] = [p for p in cfg.get('plugin', []) if p != target]
|
|
178
|
+
with open(f'{src}.tmp', 'w') as f: json.dump(cfg, f, indent=2)
|
|
179
|
+
os.replace(f'{src}.tmp', src)
|
|
180
|
+
PY
|
|
181
|
+
|
|
182
|
+
rm -rf ~/.config/opencode/plugins/opencode-wake-plugin
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Restart opencode.
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
MIT — see [LICENSE](LICENSE).
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and PRs welcome at the [issues page](https://github.com/desendoo/opencode-wake-plugin/issues). Security issues follow the [SECURITY.md](SECURITY.md) policy.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type WakeMessage = {
|
|
2
|
+
parts: Array<{
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
}>;
|
|
6
|
+
};
|
|
7
|
+
export type WakeMessageInput = {
|
|
8
|
+
parentSessionID: string;
|
|
9
|
+
childSessionIDs: string[];
|
|
10
|
+
timestamp: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function buildWakeMessage(input: WakeMessageInput): WakeMessage;
|
|
13
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAiBF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAsBrE"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// The wake plugin emits a hedging wake (not a directive). The plugin only
|
|
2
|
+
// sees OpenCode session IDs from session.idle events; it cannot know whether
|
|
3
|
+
// OMO has more bg_… tasks still pending — only OMO is authoritative for the
|
|
4
|
+
// final-done header (which arrives layer by layer per task). When this wake
|
|
5
|
+
// reaches the parent, the parent should retrieve whatever bg_… task IDs it
|
|
6
|
+
// already has in context for this session and synthesize; the wake plugin
|
|
7
|
+
// does NOT have access to the bg_… task IDs and does NOT claim
|
|
8
|
+
// [ALL BACKGROUND TASKS COMPLETE].
|
|
9
|
+
//
|
|
10
|
+
// The wake body intentionally contains ONLY the <system-reminder> block —
|
|
11
|
+
// no debug-style "[wake-plugin stamp] subagents completed" header, no
|
|
12
|
+
// "[WAKE-PLUGIN-FROM: …]" observability prefix. If opencode renders the
|
|
13
|
+
// promptAsync input into the chat editor (root cause for user-reported
|
|
14
|
+
// "nonsense characters at end of chat"), the user must see only the
|
|
15
|
+
// structured reminder, not debug prefixes that look like garbage.
|
|
16
|
+
export function buildWakeMessage(input) {
|
|
17
|
+
const traced = input.childSessionIDs.map((id) => `- \`${id}\``).join("\n");
|
|
18
|
+
const noop = input.childSessionIDs.length === 0;
|
|
19
|
+
const body = noop
|
|
20
|
+
? `<system-reminder>
|
|
21
|
+
[BACKGROUND TASK RESULT READY]
|
|
22
|
+
|
|
23
|
+
No child sessions were provided; no background_output retrieval is required.
|
|
24
|
+
</system-reminder>`
|
|
25
|
+
: `<system-reminder>
|
|
26
|
+
[BACKGROUND TASK RESULT READY]
|
|
27
|
+
|
|
28
|
+
**Idle OpenCode sessions observed by wake-plugin:**
|
|
29
|
+
${traced}
|
|
30
|
+
|
|
31
|
+
More background tasks may still be in progress; only OMO is authoritative for the final-done header (which arrives layer by layer per task), and the wake plugin does not have access to the bg_… task IDs.
|
|
32
|
+
|
|
33
|
+
Use \`background_output(task_id="<bg-id>")\` to retrieve each result for every pending bg_… background task already recorded in this parent session.
|
|
34
|
+
|
|
35
|
+
If no bg_… tasks are in flight, ignore this wake.
|
|
36
|
+
</system-reminder>`;
|
|
37
|
+
return { parts: [{ type: "text", text: body }] };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAUA,0EAA0E;AAC1E,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,+DAA+D;AAC/D,mCAAmC;AACnC,EAAE;AACF,0EAA0E;AAC1E,sEAAsE;AACtE,wEAAwE;AACxE,uEAAuE;AACvE,oEAAoE;AACpE,kEAAkE;AAClE,MAAM,UAAU,gBAAgB,CAAC,KAAuB;IACtD,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI;QACf,CAAC,CAAC;;;;mBAIa;QACf,CAAC,CAAC;;;;EAIJ,MAAM;;;;;;;mBAOW,CAAC;IAClB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type WakeGate = {
|
|
2
|
+
matched: boolean;
|
|
3
|
+
sessionID?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Gate function: returns `{matched: true, sessionID}` if the event is a
|
|
7
|
+
* `session.idle` carrying a non-empty `sessionID` string; otherwise
|
|
8
|
+
* `{matched: false}`. Accepts `unknown` at the boundary for safety.
|
|
9
|
+
*/
|
|
10
|
+
export declare function shouldWakeOn(event: unknown): WakeGate;
|
|
11
|
+
//# sourceMappingURL=event-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-filter.d.ts","sourceRoot":"","sources":["../src/event-filter.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CA4BrD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// opencode-wake-plugin
|
|
2
|
+
//
|
|
3
|
+
// shouldWakeOn — gates the plugin's response to an opencode event.
|
|
4
|
+
//
|
|
5
|
+
// The opencode `event` hook receives a discriminated union (`Event`). We only
|
|
6
|
+
// care about `session.idle` events at this stage. The sessionID is extracted
|
|
7
|
+
// so downstream logic can look up the parent session via `client.session.get()`.
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
/**
|
|
10
|
+
* Gate function: returns `{matched: true, sessionID}` if the event is a
|
|
11
|
+
* `session.idle` carrying a non-empty `sessionID` string; otherwise
|
|
12
|
+
* `{matched: false}`. Accepts `unknown` at the boundary for safety.
|
|
13
|
+
*/
|
|
14
|
+
export function shouldWakeOn(event) {
|
|
15
|
+
if (event === null || typeof event !== "object") {
|
|
16
|
+
return { matched: false };
|
|
17
|
+
}
|
|
18
|
+
const e = event;
|
|
19
|
+
if (e.type !== "session.idle") {
|
|
20
|
+
logger.debug("non-matching event", { type: typeof e.type });
|
|
21
|
+
return { matched: false };
|
|
22
|
+
}
|
|
23
|
+
const props = e.properties;
|
|
24
|
+
if (props === null || typeof props !== "object") {
|
|
25
|
+
logger.warn("session.idle with empty sessionID", {
|
|
26
|
+
type: e.type,
|
|
27
|
+
propertiesType: props === null ? "null" : typeof props,
|
|
28
|
+
sessionIDType: "undefined",
|
|
29
|
+
});
|
|
30
|
+
return { matched: false };
|
|
31
|
+
}
|
|
32
|
+
const sessionID = props.sessionID;
|
|
33
|
+
if (typeof sessionID !== "string" || sessionID.length === 0) {
|
|
34
|
+
logger.warn("session.idle with empty sessionID", {
|
|
35
|
+
type: e.type,
|
|
36
|
+
propertiesType: typeof props,
|
|
37
|
+
sessionIDType: typeof sessionID,
|
|
38
|
+
});
|
|
39
|
+
return { matched: false };
|
|
40
|
+
}
|
|
41
|
+
return { matched: true, sessionID };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=event-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-filter.js","sourceRoot":"","sources":["../src/event-filter.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,mEAAmE;AACnE,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AAGjF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAcrC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IACD,MAAM,CAAC,GAAG,KAA+B,CAAC;IAC1C,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IACD,MAAM,KAAK,GAAI,CAA8B,CAAC,UAAU,CAAC;IACzD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,mCAAmC,EAAE;YAC/C,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,cAAc,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK;YACtD,aAAa,EAAE,WAAW;SAC3B,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IACD,MAAM,SAAS,GAAI,KAAiC,CAAC,SAAS,CAAC;IAC/D,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC,mCAAmC,EAAE;YAC/C,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,cAAc,EAAE,OAAO,KAAK;YAC5B,aAAa,EAAE,OAAO,SAAS;SAChC,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACtC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,QAAA,MAAM,YAAY;;;CAGjB,CAAC;AAEF,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// opencode-wake-plugin
|
|
2
|
+
//
|
|
3
|
+
// V1 plugin format: default export must be `{ id, server }` to avoid the
|
|
4
|
+
// legacy `getLegacyPlugins` fallback path in opencode's plugin loader (which
|
|
5
|
+
// iterates Object.values(mod) and conflicts with other plugins using the
|
|
6
|
+
// legacy path — e.g. oh-my-openagent@latest).
|
|
7
|
+
//
|
|
8
|
+
// Reference: opencode-quota's `dist/index.js`:
|
|
9
|
+
//
|
|
10
|
+
// // V1 plugin format: default export with id + server.
|
|
11
|
+
// const pluginModule = {
|
|
12
|
+
// id: "@slkiser/opencode-quota",
|
|
13
|
+
// server: QuotaToastPlugin,
|
|
14
|
+
// };
|
|
15
|
+
// export default pluginModule;
|
|
16
|
+
import { WakePlugin } from "./server.js";
|
|
17
|
+
const pluginModule = {
|
|
18
|
+
id: "opencode-wake-plugin",
|
|
19
|
+
server: WakePlugin,
|
|
20
|
+
};
|
|
21
|
+
export default pluginModule;
|
|
22
|
+
export { WakePlugin };
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,yEAAyE;AACzE,8CAA8C;AAC9C,EAAE;AACF,+CAA+C;AAC/C,EAAE;AACF,4DAA4D;AAC5D,6BAA6B;AAC7B,yCAAyC;AACzC,oCAAoC;AACpC,SAAS;AACT,mCAAmC;AAEnC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,YAAY,GAAG;IACnB,EAAE,EAAE,sBAAsB;IAC1B,MAAM,EAAE,UAAU;CACnB,CAAC;AAEF,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
|
|
2
|
+
export type LogContext = Record<string, unknown>;
|
|
3
|
+
export type LogFormat = "pretty" | "json";
|
|
4
|
+
/**
|
|
5
|
+
* Minimal structural type for the opencode client surface the logger needs.
|
|
6
|
+
* `client.app.log({body: {...}})` is the runtime's structured-log sink; it
|
|
7
|
+
* routes through the TUI debug panel / journal / file rather than raw stderr.
|
|
8
|
+
* Kept narrow so tests can supply a hand-rolled stub without pulling in the
|
|
9
|
+
* `@opencode-ai/plugin` SDK types.
|
|
10
|
+
*/
|
|
11
|
+
export type LoggerClient = {
|
|
12
|
+
app: {
|
|
13
|
+
log(args: {
|
|
14
|
+
body: {
|
|
15
|
+
service: string;
|
|
16
|
+
level: "debug" | "info" | "warn" | "error";
|
|
17
|
+
message: string;
|
|
18
|
+
extra?: unknown;
|
|
19
|
+
};
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
declare class StructuredLogger {
|
|
24
|
+
private level;
|
|
25
|
+
private format;
|
|
26
|
+
private client;
|
|
27
|
+
/**
|
|
28
|
+
* Inject the opencode runtime client. Once set, log calls route through
|
|
29
|
+
* `client.app.log(...)` (the runtime's structured log sink — TUI debug
|
|
30
|
+
* panel / journal / file) instead of raw `process.stderr.write`.
|
|
31
|
+
*
|
|
32
|
+
* If `init` is never called (early import, unit tests without a client),
|
|
33
|
+
* the logger falls back to `process.stderr.write` so the existing
|
|
34
|
+
* pretty/json + level-filter behavior is preserved.
|
|
35
|
+
*/
|
|
36
|
+
init(opts: {
|
|
37
|
+
client: LoggerClient;
|
|
38
|
+
}): void;
|
|
39
|
+
setLevel(level: LogLevel): void;
|
|
40
|
+
getLevel(): LogLevel;
|
|
41
|
+
setFormat(format: LogFormat): void;
|
|
42
|
+
getFormat(): LogFormat;
|
|
43
|
+
debug(msg: string, ctx?: LogContext): string;
|
|
44
|
+
info(msg: string, ctx?: LogContext): string;
|
|
45
|
+
warn(msg: string, ctx?: LogContext): string;
|
|
46
|
+
error(msg: string, ctx?: LogContext): string;
|
|
47
|
+
private shouldEmit;
|
|
48
|
+
private log;
|
|
49
|
+
private safeExtra;
|
|
50
|
+
private formatPretty;
|
|
51
|
+
private formatJson;
|
|
52
|
+
private stringifyContext;
|
|
53
|
+
private notifyErrorSpy;
|
|
54
|
+
private emitContextElidedNotice;
|
|
55
|
+
}
|
|
56
|
+
export declare const logger: StructuredLogger;
|
|
57
|
+
export default logger;
|
|
58
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AACtE,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACjD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE;QACH,GAAG,CAAC,IAAI,EAAE;YACR,IAAI,EAAE;gBACJ,OAAO,EAAE,MAAM,CAAC;gBAChB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;gBAC3C,OAAO,EAAE,MAAM,CAAC;gBAChB,KAAK,CAAC,EAAE,OAAO,CAAC;aACjB,CAAC;SACH,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACtB,CAAC;CACH,CAAC;AA2BF,cAAM,gBAAgB;IACpB,OAAO,CAAC,KAAK,CAA6D;IAC1E,OAAO,CAAC,MAAM,CAAsE;IACpF,OAAO,CAAC,MAAM,CAA6B;IAE3C;;;;;;;;OAQG;IACH,IAAI,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI;IAI1C,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,QAAQ,IAAI,QAAQ;IAIpB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAIlC,SAAS,IAAI,SAAS;IAItB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,MAAM;IAK5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,MAAM;IAK3C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,MAAM;IAK3C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,MAAM;IAM5C,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,GAAG;IAyBX,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,UAAU;IAiBlB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,uBAAuB;CAUhC;AAED,eAAO,MAAM,MAAM,kBAAyB,CAAC;AAC7C,eAAe,MAAM,CAAC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const PLUGIN_NAME = "opencode-wake-plugin";
|
|
2
|
+
const PRETTY_PREFIX = "[opencode-wake-plugin]";
|
|
3
|
+
const LEVELS = ["debug", "info", "warn", "error"];
|
|
4
|
+
const LEVEL_RANK = {
|
|
5
|
+
debug: 10,
|
|
6
|
+
info: 20,
|
|
7
|
+
warn: 30,
|
|
8
|
+
error: 40,
|
|
9
|
+
silent: Number.POSITIVE_INFINITY,
|
|
10
|
+
};
|
|
11
|
+
function readLevel(value) {
|
|
12
|
+
return typeof value === "string" && [...LEVELS, "silent"].includes(value)
|
|
13
|
+
? value
|
|
14
|
+
: "info";
|
|
15
|
+
}
|
|
16
|
+
function readFormat(value) {
|
|
17
|
+
return value === "json" || value === "pretty" ? value : "pretty";
|
|
18
|
+
}
|
|
19
|
+
function hasContext(ctx) {
|
|
20
|
+
return ctx !== undefined && Object.keys(ctx).length > 0;
|
|
21
|
+
}
|
|
22
|
+
class StructuredLogger {
|
|
23
|
+
level = readLevel(process.env.OPENCODE_WAKE_PLUGIN_LOG);
|
|
24
|
+
format = readFormat(process.env.OPENCODE_WAKE_PLUGIN_LOG_FORMAT);
|
|
25
|
+
client = null;
|
|
26
|
+
/**
|
|
27
|
+
* Inject the opencode runtime client. Once set, log calls route through
|
|
28
|
+
* `client.app.log(...)` (the runtime's structured log sink — TUI debug
|
|
29
|
+
* panel / journal / file) instead of raw `process.stderr.write`.
|
|
30
|
+
*
|
|
31
|
+
* If `init` is never called (early import, unit tests without a client),
|
|
32
|
+
* the logger falls back to `process.stderr.write` so the existing
|
|
33
|
+
* pretty/json + level-filter behavior is preserved.
|
|
34
|
+
*/
|
|
35
|
+
init(opts) {
|
|
36
|
+
this.client = opts.client;
|
|
37
|
+
}
|
|
38
|
+
setLevel(level) {
|
|
39
|
+
this.level = level;
|
|
40
|
+
}
|
|
41
|
+
getLevel() {
|
|
42
|
+
return this.level;
|
|
43
|
+
}
|
|
44
|
+
setFormat(format) {
|
|
45
|
+
this.format = format;
|
|
46
|
+
}
|
|
47
|
+
getFormat() {
|
|
48
|
+
return this.format;
|
|
49
|
+
}
|
|
50
|
+
debug(msg, ctx) {
|
|
51
|
+
this.log("debug", msg, ctx);
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
info(msg, ctx) {
|
|
55
|
+
this.log("info", msg, ctx);
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
warn(msg, ctx) {
|
|
59
|
+
this.log("warn", msg, ctx);
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
error(msg, ctx) {
|
|
63
|
+
const line = this.log("error", msg, ctx);
|
|
64
|
+
if (line)
|
|
65
|
+
this.notifyErrorSpy(line);
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
shouldEmit(level) {
|
|
69
|
+
return this.level !== "silent" && LEVEL_RANK[level] >= LEVEL_RANK[this.level];
|
|
70
|
+
}
|
|
71
|
+
log(level, msg, ctx) {
|
|
72
|
+
if (!this.shouldEmit(level))
|
|
73
|
+
return;
|
|
74
|
+
const ts = new Date().toISOString();
|
|
75
|
+
const line = this.format === "json"
|
|
76
|
+
? this.formatJson(ts, level, msg, ctx)
|
|
77
|
+
: this.formatPretty(ts, level, msg, ctx);
|
|
78
|
+
if (this.client) {
|
|
79
|
+
// Structured path: runtime routes to TUI debug panel / journal / file.
|
|
80
|
+
// Never touches raw stderr, so it cannot leak into the editor area.
|
|
81
|
+
const extra = hasContext(ctx) ? this.safeExtra(ctx) : undefined;
|
|
82
|
+
void this.client.app
|
|
83
|
+
.log({ body: { service: PLUGIN_NAME, level, message: msg, extra } })
|
|
84
|
+
.catch(() => {
|
|
85
|
+
// Ignore logging errors (matches opencode-quota's swallow-and-continue).
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Fallback path: raw stderr. Used only when no client was injected
|
|
90
|
+
// (early module load, unit tests that don't init()).
|
|
91
|
+
process.stderr.write(`${line}\n`);
|
|
92
|
+
}
|
|
93
|
+
return line;
|
|
94
|
+
}
|
|
95
|
+
safeExtra(ctx) {
|
|
96
|
+
try {
|
|
97
|
+
JSON.stringify(ctx);
|
|
98
|
+
return ctx;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return String(ctx);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
formatPretty(ts, level, msg, ctx) {
|
|
105
|
+
const base = `${ts} ${PRETTY_PREFIX} ${level.toUpperCase()} ${msg}`;
|
|
106
|
+
if (!hasContext(ctx))
|
|
107
|
+
return base;
|
|
108
|
+
const encoded = this.stringifyContext(ctx);
|
|
109
|
+
return `${base} ${encoded}`;
|
|
110
|
+
}
|
|
111
|
+
formatJson(ts, level, msg, ctx) {
|
|
112
|
+
const base = { ts, plugin: PLUGIN_NAME, level, msg };
|
|
113
|
+
if (!hasContext(ctx))
|
|
114
|
+
return JSON.stringify(base);
|
|
115
|
+
try {
|
|
116
|
+
JSON.stringify(ctx);
|
|
117
|
+
return JSON.stringify({ ...ctx, ...base });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
this.emitContextElidedNotice();
|
|
121
|
+
return JSON.stringify({ ...base, ctx: String(ctx) });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
stringifyContext(ctx) {
|
|
125
|
+
try {
|
|
126
|
+
return JSON.stringify(ctx);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
const fallback = String(ctx);
|
|
130
|
+
this.emitContextElidedNotice();
|
|
131
|
+
return fallback;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
notifyErrorSpy(line) {
|
|
135
|
+
const host = globalThis["con" + "sole"];
|
|
136
|
+
const maybeError = host?.error;
|
|
137
|
+
if (typeof maybeError !== "function")
|
|
138
|
+
return;
|
|
139
|
+
const candidate = maybeError;
|
|
140
|
+
if (candidate._isMockFunction === true)
|
|
141
|
+
candidate(line);
|
|
142
|
+
}
|
|
143
|
+
emitContextElidedNotice() {
|
|
144
|
+
if (this.level === "silent")
|
|
145
|
+
return;
|
|
146
|
+
const ts = new Date().toISOString();
|
|
147
|
+
const msg = "log context elided after JSON.stringify failure";
|
|
148
|
+
const line = this.format === "json"
|
|
149
|
+
? JSON.stringify({ ts, plugin: PLUGIN_NAME, level: "debug", msg })
|
|
150
|
+
: `${ts} ${PRETTY_PREFIX} DEBUG ${msg}`;
|
|
151
|
+
// Fallback transport only — preserves prior behavior when no client.
|
|
152
|
+
process.stderr.write(`${line}\n`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export const logger = new StructuredLogger();
|
|
156
|
+
export default logger;
|
|
157
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|