@loicngr/kobo 1.7.15 → 1.7.16

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 CHANGED
@@ -4,6 +4,10 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 1.7.16
8
+
9
+ - feat(engine): handle user interruptions as clean stops
10
+
7
11
  ## 1.7.15
8
12
 
9
13
  - docs: document new settings and features
@@ -154,10 +154,14 @@ export function createClaudeCodeEngine() {
154
154
  // event-mapper already surfaced an `error` event but the iterator
155
155
  // still terminated naturally. Reflect that in the session:ended
156
156
  // reason so the orchestrator transitions the workspace to `error`.
157
+ // A user soft-interrupt also drains naturally (the SDK emits
158
+ // `error_during_execution`, which the mapper suppresses) — report
159
+ // it as `killed`, consistent with the catch-block abort path.
160
+ const endReason = userInterrupted ? 'killed' : mapperState.sawErrorResult ? 'error' : 'completed';
157
161
  safeEmit({
158
162
  kind: 'session:ended',
159
- reason: mapperState.sawErrorResult ? 'error' : 'completed',
160
- exitCode: mapperState.sawErrorResult ? null : 0,
163
+ reason: endReason,
164
+ exitCode: endReason === 'completed' ? 0 : null,
161
165
  });
162
166
  }
163
167
  catch (err) {
@@ -212,6 +216,10 @@ export function createClaudeCodeEngine() {
212
216
  },
213
217
  interrupt() {
214
218
  userInterrupted = true;
219
+ // The SDK ends an interrupted run by emitting a `result` with
220
+ // subtype `error_during_execution` through the normal iterator —
221
+ // the mapper needs this flag to treat it as a clean stop.
222
+ mapperState.userInterrupted = true;
215
223
  const qq = q;
216
224
  if (typeof qq.interrupt === 'function') {
217
225
  try {
@@ -83,6 +83,7 @@ export function createMapperState() {
83
83
  openMessages: new Map(),
84
84
  sawErrorResult: false,
85
85
  quotaErrorEmitted: false,
86
+ userInterrupted: false,
86
87
  };
87
88
  }
88
89
  /** Known SDK `result` subtypes that indicate the run failed. */
@@ -290,7 +291,12 @@ export function mapSdkMessage(msg, state) {
290
291
  // the orchestrator can transition the workspace to `error` instead of
291
292
  // `completed`. The flag on `state` lets the engine override the
292
293
  // post-loop session:ended reason.
293
- if (isErrorResultSubtype(subtype)) {
294
+ // A user soft-interrupt ends the run with `error_during_execution`
295
+ // through this normal iterator path. That is a clean stop, not a
296
+ // failure — skip the error event so the UI shows no red banner and the
297
+ // workspace is not pushed into `error`.
298
+ const isInterruptedStop = state.userInterrupted && subtype === 'error_during_execution';
299
+ if (isErrorResultSubtype(subtype) && !isInterruptedStop) {
294
300
  state.sawErrorResult = true;
295
301
  const detail = (typeof parsed.error === 'string' && parsed.error) || (typeof parsed.result === 'string' && parsed.result) || '';
296
302
  const isQuota = QUOTA_PATTERN.test(detail);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.7.15",
3
+ "version": "1.7.16",
4
4
  "description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",