@knightcodeai/cli 0.18.4 → 0.20.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 (73) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/bundle/chunks/anthropic-messages-5ZM7Z2XQ.js +6 -0
  3. package/dist/bundle/chunks/azure-openai-responses-OUS4VJNA.js +2 -0
  4. package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
  5. package/dist/bundle/chunks/chunk-5ZES34QN.js +2 -0
  6. package/dist/bundle/chunks/{chunk-3VVG5HD5.js → chunk-6GRVIFTN.js} +2 -2
  7. package/dist/bundle/chunks/{chunk-FG6M5B3F.js → chunk-6KWPS3LA.js} +159 -124
  8. package/dist/bundle/chunks/chunk-6X7IXVQH.js +30 -0
  9. package/dist/bundle/chunks/{chunk-T6M3FJYR.js → chunk-B7A4GIBK.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-3B66IKOZ.js → chunk-BWGDAXFH.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-MXIWLMNT.js → chunk-CCP7VAJU.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-L2XRQUGK.js → chunk-DFRYMT3C.js} +4 -4
  13. package/dist/bundle/chunks/{chunk-NGWSYXD5.js → chunk-IIWY7U5S.js} +3 -3
  14. package/dist/bundle/chunks/{chunk-SLWTIEJK.js → chunk-J63X3FRT.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-HRZGVTRM.js → chunk-JMJS643J.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-GCXF7ZNR.js → chunk-L6QGGIZG.js} +4 -4
  17. package/dist/bundle/chunks/chunk-MZ5UVOTP.js +2 -0
  18. package/dist/bundle/chunks/chunk-N5M3XNPN.js +2 -0
  19. package/dist/bundle/chunks/{chunk-XILUO467.js → chunk-RKNFSQUG.js} +1 -1
  20. package/dist/bundle/chunks/chunk-UU4A4NV5.js +2 -0
  21. package/dist/bundle/chunks/{chunk-2QXPCKUQ.js → chunk-VQBEVSZH.js} +1 -1
  22. package/dist/bundle/chunks/{cli-AMSWLH7A.js → cli-72YA573P.js} +1 -1
  23. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-AWHNPEAT.js +2 -0
  24. package/dist/bundle/chunks/{easter-egg-3d-VH6SGOZS.js → easter-egg-3d-XXPCDCZV.js} +1 -1
  25. package/dist/bundle/chunks/{execute-4UQWU4SD.js → execute-KWIUMPDE.js} +1 -1
  26. package/dist/bundle/chunks/github-copilot.js +1 -1
  27. package/dist/bundle/chunks/google-generative-ai-HPLZ2ABU.js +2 -0
  28. package/dist/bundle/chunks/google-vertex-E3UTTTIG.js +2 -0
  29. package/dist/bundle/chunks/knightcode-messages-57CV36UV.js +8 -0
  30. package/dist/bundle/chunks/{llama-cpp-classify-HRPRKOZA.js → llama-cpp-classify-FEWC5ZIH.js} +2 -2
  31. package/dist/bundle/chunks/{mistral-conversations-NUSSE6NX.js → mistral-conversations-KT4C4XTZ.js} +1 -1
  32. package/dist/bundle/chunks/openai-codex-responses-BCLSSOJ6.js +10 -0
  33. package/dist/bundle/chunks/openai-completions-GTNAEXX6.js +7 -0
  34. package/dist/bundle/chunks/{openai-decisions-ENPSYANO.js → openai-decisions-CSQTW3S4.js} +1 -1
  35. package/dist/bundle/chunks/openai-responses-ILFXLMJW.js +4 -0
  36. package/dist/bundle/chunks/openrouter-images-J66HXAMB.js +2 -0
  37. package/dist/bundle/chunks/{runtime-IU2Z5WWF.js → runtime-OW5JKWBE.js} +1 -1
  38. package/dist/bundle/chunks/typesafe-system-one-QA5VXBEZ.js +2 -0
  39. package/dist/bundle/chunks/{virtual-modules-AK4BYDR6.js → virtual-modules-5DLTLPP7.js} +1 -1
  40. package/dist/bundle/cli-runtime.js +1 -1
  41. package/dist/core/export-html/template.css +9 -0
  42. package/dist/core/export-html/template.js +165 -45
  43. package/docs/app-server.md +350 -12
  44. package/docs/app-server.schema.json +11493 -2563
  45. package/docs/containerization.md +2 -2
  46. package/docs/declarative-views.md +118 -0
  47. package/docs/desktop.md +45 -4
  48. package/docs/docs.json +4 -0
  49. package/docs/extensions.md +4 -2
  50. package/docs/models-configuration.md +9 -0
  51. package/docs/models.md +8 -4
  52. package/docs/settings.md +53 -0
  53. package/examples/extensions/README.md +1 -0
  54. package/examples/extensions/desktop-view.ts +72 -0
  55. package/examples/extensions/gondolin/index.ts +8 -10
  56. package/examples/extensions/gondolin/package-lock.json +20 -30
  57. package/examples/extensions/gondolin/package.json +1 -1
  58. package/package.json +1 -1
  59. package/dist/bundle/chunks/anthropic-messages-VD2GN7C4.js +0 -6
  60. package/dist/bundle/chunks/azure-openai-responses-6IJ5JZ3Y.js +0 -2
  61. package/dist/bundle/chunks/chunk-3PSTVJEH.js +0 -2
  62. package/dist/bundle/chunks/chunk-4DOSUUX3.js +0 -30
  63. package/dist/bundle/chunks/chunk-CRLO4X52.js +0 -2
  64. package/dist/bundle/chunks/chunk-HHWP56PR.js +0 -2
  65. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-JBMTTHVI.js +0 -2
  66. package/dist/bundle/chunks/google-generative-ai-QBWYWMCA.js +0 -2
  67. package/dist/bundle/chunks/google-vertex-4KVZWEAI.js +0 -2
  68. package/dist/bundle/chunks/knightcode-messages-3GNOTMSI.js +0 -8
  69. package/dist/bundle/chunks/openai-codex-responses-66UQNGWB.js +0 -10
  70. package/dist/bundle/chunks/openai-completions-QTYKORY2.js +0 -7
  71. package/dist/bundle/chunks/openai-responses-JSX72BKV.js +0 -3
  72. package/dist/bundle/chunks/openrouter-images-ZI7LLZ4Y.js +0 -2
  73. package/dist/bundle/chunks/typesafe-system-one-ETHWOYKC.js +0 -2
@@ -63,7 +63,8 @@ Start a thread with its project directory:
63
63
  ```
64
64
 
65
65
  Wait for each response. `thread/start` and `thread/fork` return
66
- `{thread, warnings}`. A fork is named after its source with ` (fork)` appended. `thread/resume` also returns warnings; render them from the
66
+ `{thread, warnings, ui}` (`ui` is the thread's extension UI, see
67
+ [Dialogs and extension UI](#dialogs-and-extension-ui)). A fork is named after its source with ` (fork)` appended. `thread/resume` also returns warnings; render them from the
67
68
  response, because they can occur before subscription. A warning has
68
69
  `{message, source, path?, line?, column?}`, where `source` is `settings`,
69
70
  `extension`, `trust`, or `server`. Malformed settings, refused extension loads,
@@ -72,9 +73,10 @@ send a subscriber-only `warning` with `threadId`.
72
73
 
73
74
  Threads and list summaries include `createdAt` (header creation time) and
74
75
  optional `forkedFromId` (the parent's header ID, not a UUID inferred from its
75
- filename). List summaries use file modification time for `updatedAt`; loaded
76
- threads use the latest entry time, falling back to the header. Their `status`
77
- is one of:
76
+ filename). A loaded thread also carries `sessionFile`, the session file's path,
77
+ once it has been saved. List summaries use file modification time for
78
+ `updatedAt`; loaded threads use the latest entry time, falling back to the
79
+ header. Their `status` is one of:
78
80
 
79
81
  - `{type: "notLoaded"}`
80
82
  - `{type: "idle"}`
@@ -159,9 +161,12 @@ lands in a later turn. A steer cannot be taken back once accepted.
159
161
  input starts a turn of its own once the thread is idle, oldest first; on an idle
160
162
  thread it starts at once. Until it starts it is removable with
161
163
  `turn/queue/remove`. A queued input that fails to start (no model, signed out) is
162
- dropped with an `error` notification. `turn/queue/updated` sends
163
- `{threadId, steering: [text], followUp: [{id, text}]}`: steers the agent has not
164
- delivered yet, and the queued inputs.
164
+ dropped with an `error` notification carrying its `clientMessageId`, if it had
165
+ one. `turn/queue/updated` sends
166
+ `{threadId, steering: [text], followUp: [{id, text, attachments?}]}`: steers the
167
+ agent has not delivered yet, and the queued inputs. `text` is the user's own (not
168
+ a prompt composed with review comments); `attachments` counts the images and
169
+ review comments an input carries that its text alone does not.
165
170
 
166
171
  `turn/interrupt` accepts optional `turnId`; a supplied stale ID returns
167
172
  `turnMismatch`, while omitting it retains interrupt-current behavior. It drops
@@ -201,9 +206,9 @@ Desktop OS notifications are for live outcomes, not initial history.
201
206
  `thread/resume` returns history and subscribes in the same synchronous send step.
202
207
  Its separate `runningTurn` is a current snapshot, even when an old history cursor
203
208
  is supplied. Its required `queue` and `usage` are also live send-time snapshots,
204
- not values from handler entry or the history cursor. Buffer queue/usage events
205
- while awaiting the response, install the snapshots (an empty queue clears stale
206
- state), then replay the buffered events. Invalid cursors subscribe nobody.
209
+ not values from handler entry or the history cursor; so is `ui`. Buffer
210
+ queue/usage/`ui/*` events while awaiting the response, install the snapshots (an
211
+ empty queue clears stale state), then replay the buffered events. Invalid cursors subscribe nobody.
207
212
  Install `runningTurn` before applying buffered deltas. `thread/replaced` moves
208
213
  subscriptions to the new thread; captured old extension contexts become stale.
209
214
  `thread/unsubscribe` returns `{status: "unsubscribed" | "notSubscribed"}`.
@@ -281,6 +286,117 @@ The most recent initiating subscriber is asked first. Disconnect or unsubscribe
281
286
  cancels that attempt and migrates it to another subscriber; no subscribers means
282
287
  cancel. `serverRequest/cancelled` withdraws an outstanding request.
283
288
 
289
+ ## Dialogs and extension UI
290
+
291
+ What KnightCode asks the user mid-turn reaches a desktop connection as a server
292
+ request, asked the way approvals are; the thread reports `waitingOn: ["dialog"]`
293
+ meanwhile.
294
+
295
+ `item/toolCall/requestUserInput` carries `ask_user`'s questions, all at once,
296
+ with the `itemId` of the call asking, so a client can move back and forth
297
+ between them before answering. Each question has an `id`, its text and its
298
+ options (`label`, `description`); a label ending in `(Recommended)` is the
299
+ recommended one. Reply `{"answers": {"<id>": {"label": "..."}}}` with the
300
+ chosen option's exact label and an optional `note`, or `{"other": "..."}` for
301
+ the user's own answer; leave out a skipped question. `{}` dismisses the
302
+ questions and stops the turn.
303
+
304
+ `ui/dialog` carries the rest, such as the classifier gate's confirmation of a
305
+ risky call and plan mode's review. `kind` says which: a select carries
306
+ `options`, a confirm its `message`, an input its `placeholder`, an editor its
307
+ `prefill`. Reply with `{"value": "..."}` for a select, input or editor,
308
+ `{"confirmed": true}` for a confirm, or `{}` to cancel.
309
+
310
+ Notices such as "Downloaded model" arrive as the notification `ui/notify` with
311
+ a `level` of `info`, `warning` or `error`.
312
+
313
+ What an extension shows beside the transcript is the thread's extension UI:
314
+
315
+ - The `ui` of the `thread/start`, `thread/resume` and `thread/fork` results is
316
+ all of it as it stands when the response is written:
317
+ `{statuses: [{key, text}], title?, workingMessage?, workingVisible, widgets}`.
318
+ Statuses are sorted by key. Each widget is `{key, placement, lines}` or
319
+ `{key, placement, view}`, `placement` being `aboveEditor` or `belowEditor`;
320
+ those above come first, each group in the order they were last set.
321
+ - `ui/state` carries every field but the widgets, whole, whenever one changes.
322
+ An absent `title` or `workingMessage` is a cleared one.
323
+ `workingVisible: false` asks the client to hide its waiting indicator, and
324
+ `workingMessage` replaces its text.
325
+ - `ui/widget` carries one widget set or replaced (it moves to the end of its
326
+ placement), or `{threadId, key}` alone when it is removed.
327
+
328
+ Install `ui` from the result, then apply the notifications, buffered across a
329
+ resume as `queue` is. A `/reload` clears everything the old extensions showed,
330
+ with one `ui/state` and a removal per widget, before the new ones start. The
331
+ server strips ANSI escapes and keeps text on one line, at most 200 code points;
332
+ widget lines keep their spacing. A [view](declarative-views.md) is data, never
333
+ HTML; the server checked it before sending.
334
+
335
+ A button or field in a widget's view answers with the request `ui/event`
336
+ (below), which reaches every handler the thread's extensions registered with
337
+ `ctx.ui.onViewEvent`. Views on `toolCall` and `extensionMessage` items (their
338
+ optional `view`) are for display only.
339
+
340
+ Widget factories, `custom()` components, header and footer components, raw
341
+ terminal input and the working indicator draw only in the terminal: the server
342
+ writes one line to its log per method per thread load and draws nothing. Editor
343
+ access (`setEditorText`, `getEditorText`, `pasteToEditor`) does nothing.
344
+ Threads a terminal-mode connection loads have no dialogs or extension UI yet.
345
+
346
+ ## Workspaces, diffs and git
347
+
348
+ A thread runs in a workspace: the user's checkout (`{mode: "local"}`), or a git
349
+ worktree of its own (`{mode: "worktree"}`), as in the Codex desktop app.
350
+
351
+ `thread/start {cwd, workspace: {mode: "worktree", baseRef?, carryLocalChanges?, environment?}}`
352
+ creates a worktree on a **detached `HEAD`** at `baseRef` (default:
353
+ the checkout's `HEAD`) under `<worktrees.root>/<4 hex digits>/<repository name>`
354
+ and runs the thread there, in the same subdirectory the `cwd` had. No branch is
355
+ created until `git/branch/create` or a handoff makes one. `carryLocalChanges`
356
+ brings the checkout's uncommitted changes (untracked files included, ignored
357
+ files not) along as uncommitted edits. Creation runs no hook, filter or user
358
+ code, so a failed creation leaves nothing behind. A start whose `cwd` is inside
359
+ a managed worktree is a new thread in that worktree.
360
+
361
+ `thread/workspace/progress {threadId, phase, output}` streams the output of an
362
+ environment's setup or cleanup script to the connection that asked. Every
363
+ `Thread` carries `workspace`; `ThreadSummary.workspaceMode` marks worktree
364
+ threads in listings.
365
+
366
+ Diffs come in Codex's Changes scopes: `lastTurn`, `uncommitted` (the default),
367
+ `unstaged`, `staged`, `committed` and `branch`. Each hunk has a content-hash id;
368
+ an action naming a hunk that changed since it was read answers `gitConflict`
369
+ with `data.reason: "diffChanged"`. Files changed while a turn ran are reported
370
+ by `turn/diff/updated {threadId, turnId, files}` after `turn/completed`, each
371
+ file as `{path, additions, deletions}`: the net line totals between the working
372
+ tree before and after the turn. A read-back turn carries the same list as
373
+ `Turn.files`. This
374
+ means "changed while the turn ran", not "changed by the turn": an edit another
375
+ local thread or an editor made meanwhile is in the list too. A worktree thread
376
+ is the only writer in its worktree, so its list is its own.
377
+
378
+ Review comments are drafts the client keeps. They travel with the next
379
+ `turn/start` or `turn/queue/add` as `comments`; the server quotes the commented
380
+ lines from the diff the user reviewed, and the user item shows the comments
381
+ next to the typed text.
382
+
383
+ **Reverting an added file deletes it**, an untracked one included. Clients
384
+ confirm with the user before a revert that removes files.
385
+
386
+ `thread/handoff` moves a chat and its uncommitted changes between its worktree
387
+ and the main checkout. The destination must be clean: "Stash or commit your
388
+ local changes to hand off." Archiving a thread deletes its worktree after a
389
+ snapshot, kept as `refs/knightcode/snapshots/<bucket>/<time>`, and
390
+ `thread/worktree/restore` rebuilds it. Past `worktrees.keep` worktrees, the
391
+ least recently used are pruned the same way. Outside a git repository, diff and
392
+ git methods answer `notGitRepo`.
393
+
394
+ `gitConflict` errors carry `data.reason`: `diffChanged`, `patchFailed`,
395
+ `concurrentChange`, `pushRejected`, `localChanges`, `operationInProgress`,
396
+ `branchExists`, `recoveryRequired`, `worktreeDeleted`, `worktreeMissing`,
397
+ `worktreeInUse` or `submodules`, with `paths`, `output` or
398
+ `recoveryInstructions` where they apply.
399
+
284
400
  ## Method reference
285
401
 
286
402
  All methods require the handshake. Common errors are `notInitialized`,
@@ -300,12 +416,13 @@ Errors: `threadNotFound`.
300
416
 
301
417
  ### `config/read`
302
418
 
303
- Params: optional `{cwd}`. Result: `{settings, origins, versions, errors}`.
419
+ Params: optional `{cwd}`. Result: `{settings, defaults, origins, versions, paths, errors}`.
304
420
  Without cwd, only global settings are read. With cwd, trusted project settings
305
421
  merge over global settings; untrusted project files are not read. `origins`
306
422
  maps dotted leaf keys to `global` or `project`. `versions.global` and optional
307
423
  `versions.project` are SHA-256 hashes of raw file contents, or `"none"` for an
308
- absent or unread untrusted scope. Settings, origins, versions, and parse errors
424
+ absent or unread untrusted scope. `defaults` is what an unset key means, in the
425
+ shape of `settings`; `paths` names the global and, with cwd, the project file. Settings, origins, versions, and parse errors
309
426
  come from the same scope reads, not cached settings paired with fresh versions.
310
427
  Parse errors appear as structured `source: "settings"` warnings with an
311
428
  optional path and available one-based line/column; unaffected scopes still read.
@@ -336,6 +453,71 @@ malformed keys, and prototype-pollution segments (`__proto__`, `constructor`,
336
453
  {"id":12,"method":"config/write","params":{"scope":"global","key":"theme","value":"dark","expectedVersion":"none"}}
337
454
  ```
338
455
 
456
+ ### `tool/list`
457
+
458
+ Params: optional `{cwd}`. Result: `{tools: [{name, description, source}], defaultTools}`.
459
+ The tools a new thread in `cwd` would register (without cwd, a
460
+ project-less one), from an in-memory session like `command/list`'s; hidden tools
461
+ are left out, and MCP tools appear only once a thread connects. `defaultTools` is
462
+ the global `defaultTools` setting resolved (`+name` and `-name` applied), or the
463
+ built-in default `read`, `bash`, `edit`, `write`. Change it with `config/write`.
464
+
465
+ ```json
466
+ {"id":26,"method":"tool/list","params":{}}
467
+ ```
468
+
469
+ ### `tool/settings/read` and `tool/settings/write`
470
+
471
+ The KnightCode tools `/tools` manages, stored in `tools.json` in the agent
472
+ directory. `tool/settings/read {}` returns
473
+ `{tools: [{name, enabled, settings}], classifiers: [{provider, id, name}]}`: each tool's state (its default when the
474
+ file does not set it) and its own settings, with values of keys ending in `Key`
475
+ masked to their last four characters; `classifiers` are the classifier models
476
+ with credentials, for the classifier gate's `model`.
477
+ `tool/settings/write {name, key, value}` sets one setting; `enabled` takes a boolean, other keys a string,
478
+ and `null` deletes the key. An unknown tool or a malformed key is
479
+ `invalidParams`.
480
+
481
+ ```json
482
+ {"id":27,"method":"tool/settings/write","params":{"name":"websearch","key":"provider","value":"brave"}}
483
+ ```
484
+
485
+ ### `mcp/list`, `mcp/update`, `mcp/add` and `mcp/remove`
486
+
487
+ The servers of the global `mcp.json` and, for an explicitly trusted project, its
488
+ `.knightcode/mcp.json` ([MCP](mcp.md)). `mcp/list {cwd?}` returns
489
+ `{servers, errors, paths}`: each server's `name`, `scope`, defining file (`source`), the
490
+ project file that overrides it (`override`), `transport` (`stdio` or `http`),
491
+ `target` (command line or URL), `description`, `enabled` and `exposure`.
492
+ `mcp/update {cwd?, name, enabled?, exposure?}` saves the change where `/mcp`
493
+ would: in the project override when there is one, else in the defining file.
494
+ `mcp/add {scope, cwd?, name, config}` validates `config` as an `mcpServers` entry
495
+ and adds or replaces it (result `{replaced}`); `auth` is refused in a project
496
+ file. `mcp/remove {scope, cwd?, name}` returns `{removed}`. Project writes need
497
+ cwd and a trusted project (`invalidParams`, `data.reason: "untrusted"`).
498
+ Connections, sign-in and status belong to the MCP extension in each thread
499
+ (`/mcp`); loaded threads pick up file changes when they next load.
500
+
501
+ ```json
502
+ {"id":28,"method":"mcp/add","params":{"scope":"global","name":"docs","config":{"url":"https://example.com/mcp"}}}
503
+ ```
504
+
505
+ ### `resource/list` and `resource/set`
506
+
507
+ Extensions, skills, prompt templates and themes as `knightcode config` lists them.
508
+ `resource/list {cwd?}` returns
509
+ `{resources: [{type, path, name, enabled, scope, origin, source}]}` for what a thread in `cwd` (or with no project) would find,
510
+ installing nothing. Built-in extensions are left out: they are always on, so
511
+ `resource/set` refuses them like any unknown path. `resource/set {cwd?, type, path, enabled}` loads or
512
+ skips one resource with a `+` or `-` pattern in the settings of the scope that
513
+ defines it: a top-level resource in that scope's `extensions`, `skills`,
514
+ `prompts` or `themes`, a package's in that package's filter. Project resources
515
+ need a trusted project; an unknown path is `invalidParams`.
516
+
517
+ ```json
518
+ {"id":29,"method":"resource/set","params":{"type":"skills","path":"/home/u/.knightcode/agent/skills/review/SKILL.md","enabled":false}}
519
+ ```
520
+
339
521
  ### `project/trust/read`
340
522
 
341
523
  Params: `{cwd}`. Result: `{trusted, needsTrust}`. `trusted` is true, false, or
@@ -394,6 +576,24 @@ and nothing is written. Built-in commands that only work in the terminal
394
576
  {"id":17,"method":"command/list","params":{"cwd":"/path/to/project"}}
395
577
  ```
396
578
 
579
+ ### `ui/event`
580
+
581
+ Params: `{threadId, widgetKey, id, value?}`. Result: `{}`. A press of the button
582
+ `id`, or a change of the field `id`, in the widget `widgetKey`'s view: `value` is
583
+ a string for a text or select field, a boolean for a toggle, and absent for a
584
+ button. Every handler of the thread gets `{widgetKey, id, value?}`; a handler
585
+ that fails becomes a `warning`. The extension answers by setting the widget
586
+ again: a field shows the view's value, not what was typed. A press of a widget or
587
+ control that is gone answers `{}` and reaches nobody.
588
+
589
+ Errors: `threadNotFound` when the thread is not loaded, and `invalidParams` when
590
+ the value does not fit the control (a toggle's string, a select value outside its
591
+ options, a button with a value).
592
+
593
+ ```json
594
+ {"id":18,"method":"ui/event","params":{"threadId":"THREAD_ID","widgetKey":"checks","id":"verbose","value":true}}
595
+ ```
596
+
397
597
  ### `thread/delete`
398
598
 
399
599
  Params: `{threadId}`. Result: `{}`. Permanently removes an archived transcript;
@@ -425,6 +625,136 @@ Diagnostics observe the shared server, not only this connection.
425
625
  {"id":18,"method":"server/diagnostics","params":{}}
426
626
  ```
427
627
 
628
+ ### `thread/diff/read`
629
+
630
+ Params: `{threadId, scope?, against?, commit?, context?, ignoreWhitespace?}`.
631
+ Result: `{scope, root, files, filesOmitted?, untrackedSkipped?, turnId?, stale?, against?}`.
632
+ `files` are `{path, oldPath?, status, binary, additions, deletions, tooLarge?, hunks}`,
633
+ their paths relative to the repository's `root` (not the thread's cwd, which can
634
+ be a subdirectory of it); each hunk keeps its lines with their `" "`, `"+"`, `"-"` or
635
+ `"\"` prefix. `committed` needs `commit`; `branch` compares with `against`,
636
+ else the worktree's base, the upstream or the default branch, and echoes it.
637
+ More than 500 untracked files are counted in `untrackedSkipped` and left out.
638
+ Files past the response budget come without hunks (`tooLarge`) or are only
639
+ counted (`filesOmitted`). A hunk id hashes the file's header (its blob ids) and
640
+ the hunk at the read's `context`: an action must pass the same `context` (a
641
+ `context: 0` id matches no action), and after an action changes a file, read it
642
+ again, as its other hunks' ids change too. A hunk id from an `ignoreWhitespace` read matches no
643
+ action. Reads take no lock and never write the user's index.
644
+ Errors: `threadNotFound`, `notGitRepo`; `invalidParams` for a missing `commit`
645
+ or no branch to compare with.
646
+
647
+ ```json
648
+ {"id":20,"method":"thread/diff/read","params":{"threadId":"THREAD_ID","scope":"uncommitted","context":3}}
649
+ ```
650
+
651
+ ### `thread/diff/revert`
652
+
653
+ Params: `{threadId, scope?, against?, commit?, path?, hunkId?, context?, reapply?, turnId?}`.
654
+ With `turnId` (`lastTurn` only), a last turn other than that one answers
655
+ `gitConflict` `diffChanged`, so an Undo shown on one turn never reverts a newer one.
656
+ Result: `{paths}`. Reverse-applies one hunk, one file, or the whole scope to the
657
+ working tree; `lastTurn` without a path undoes the turn, and only while every
658
+ affected file is exactly as the turn left it. With `reapply` (`lastTurn` only,
659
+ no hunk), the turn's changes apply again instead, and only while every affected
660
+ file is exactly as the turn found it. Hunks of renamed, added, deleted,
661
+ binary or mode-changed files revert as whole files only. Refused while a turn
662
+ runs in the checkout; no turn starts and no tool runs there until it ends.
663
+ Errors: `turnInProgress`; `invalidParams` for `staged` and `committed` or a
664
+ path outside the diff; `gitConflict` with `diffChanged` or `patchFailed`.
665
+
666
+ ```json
667
+ {"id":21,"method":"thread/diff/revert","params":{"threadId":"THREAD_ID","path":"src/a.ts","hunkId":"HUNK_ID"}}
668
+ ```
669
+
670
+ ### `thread/handoff`
671
+
672
+ Params: `{threadId, to: "local" | "worktree", branch?, environment?}`. Result:
673
+ `{thread, branch?, warnings}`. To the main checkout: the worktree's `HEAD`
674
+ becomes a branch checked out there (the worktree's own branch, else `branch`,
675
+ else the previous handoff's branch fast-forwarded, else
676
+ `<worktrees.branchPrefix><title>`), the uncommitted changes move, and the
677
+ worktree stays, detached and clean. Back: the worktree is detached at the main
678
+ checkout's `HEAD` and the changes move into it; a thread born in the main
679
+ checkout gets a worktree. The changes are saved under `<agentDir>/handoff/`
680
+ before anything moves and leave the source only after they landed. The thread
681
+ is reloaded where it now runs: subscribers receive `thread/replaced` with the
682
+ same id, and queued inputs follow it.
683
+ Errors: `turnInProgress`; `invalidParams` for a thread with no message yet or
684
+ already on that side; `gitConflict` with `localChanges`,
685
+ `operationInProgress`, `branchExists`, `recoveryRequired` (with `data.patch`)
686
+ or `worktreeDeleted`.
687
+
688
+ ```json
689
+ {"id":22,"method":"thread/handoff","params":{"threadId":"THREAD_ID","to":"local"}}
690
+ ```
691
+
692
+ ### `thread/worktree/restore`
693
+
694
+ Params: `{threadId}`. Result: `{thread}`. Rebuilds a deleted worktree at its
695
+ old path from its snapshot: detached at the old `HEAD`, with the working files
696
+ and the staged state as they were (ignored files were not kept). Then the
697
+ thread loads there.
698
+ Errors: `threadNotFound`; `invalidParams` when the worktree is not deleted;
699
+ `gitConflict` with `worktreeMissing` (deleted without a snapshot: fork the chat
700
+ into a new worktree instead) or `submodules`.
701
+
702
+ ```json
703
+ {"id":23,"method":"thread/worktree/restore","params":{"threadId":"THREAD_ID"}}
704
+ ```
705
+
706
+ ### `worktree/list` and `worktree/delete`
707
+
708
+ `worktree/list {cwd?}` →
709
+ `{worktrees: [{path, projectCwd, head?, branch?, threadIds, lastUsedAt, deleted}]}`,
710
+ the managed worktrees (of `cwd`'s project
711
+ when given). `worktree/delete {path}` → `{snapshot?}` deletes one after its
712
+ snapshot. A worktree a thread is loaded in, or one holding a nested repository
713
+ (an initialized submodule or a repository inside it), is never deleted: archive
714
+ and pruning skip it, and `worktree/delete` answers `gitConflict` with
715
+ `worktreeInUse` or `submodules`. Only registrations in the managed layout are
716
+ removed; the user's own worktrees are never touched.
717
+
718
+ ```json
719
+ {"id":24,"method":"worktree/delete","params":{"path":"/home/u/.knightcode/agent/worktrees/ab12/project"}}
720
+ ```
721
+
722
+ ### `environment/list` and `environment/save`
723
+
724
+ `environment/list {cwd}` → `{environments, trusted}` reads the project's
725
+ `.knightcode/environments.json`; `environment/save {cwd, environments}` writes
726
+ it (names unique, at most 50). An untrusted project's scripts never run. See
727
+ [Settings](settings.md#worktrees).
728
+
729
+ ```json
730
+ {"id":25,"method":"environment/save","params":{"cwd":"/p","environments":[{"name":"node","setup":{"default":"cd \"$KNIGHTCODE_WORKTREE_PATH\" && npm ci"}}]}}
731
+ ```
732
+
733
+ ### `git/*`
734
+
735
+ Each acts on the thread's own checkout: its worktree, or the user's checkout.
736
+ `git/status` → branch, upstream, ahead/behind, staged/unstaged/untracked/
737
+ conflicted counts and an operation in progress. `git/stage` and `git/unstage`
738
+ take `{threadId, path?, hunkId?, context?}`: no path is everything; paths are
739
+ literal. `git/commit {threadId, message?, includeUnstaged?}` stages everything
740
+ first unless `includeUnstaged: false`; an empty message is written by the
741
+ thread's model from the staged diff, outside the conversation, and the commit
742
+ answers `concurrentChange` when the checkout changed meanwhile. The user's
743
+ hooks run. `git/push` pushes the current branch with one explicit,
744
+ non-forcing refspec and sets the upstream the first time.
745
+ `git/pr/create {threadId, title?, body?, draft?}` runs `gh pr create` (`toolMissing` without
746
+ `gh`). `git/branches`, `git/branch/create {name}` ("Create branch here"),
747
+ `git/branch/switch {name}` (refused when it would overwrite local changes or an
748
+ ignored file the branch tracks) and `git/log {limit?}` (the commits since the
749
+ base, for the `committed` scope) complete the set.
750
+ Errors: `notGitRepo`, `invalidParams`, `authRequired`, `toolMissing`;
751
+ `gitConflict` with `diffChanged`, `patchFailed`, `concurrentChange`,
752
+ `pushRejected`, `branchExists` or `localChanges`.
753
+
754
+ ```json
755
+ {"id":26,"method":"git/commit","params":{"threadId":"THREAD_ID","includeUnstaged":true}}
756
+ ```
757
+
428
758
  ## Ownership, unloading, and shutdown
429
759
 
430
760
  Only one process may own a persisted session file. Symlink/junction aliases share
@@ -461,6 +791,14 @@ Limits count UTF-8 bytes:
461
791
  - 1 MiB per history/list response, including envelope reserve.
462
792
  - 64 KiB per inline turn/item; larger values use references.
463
793
  - 48 KiB per payload chunk before base64 encoding.
794
+ - 64 KiB per view in a tool result or an extension message, as serialized JSON;
795
+ a larger or invalid view is dropped and the raw output or text shows.
796
+ - 16 KiB per widget (its lines or view), 16 widgets and 50 statuses per thread;
797
+ status and widget keys of 1 to 128 characters; status text, title and working
798
+ message cut to 200 code points. A refused widget or status gives one
799
+ `warning` per key per load (a load warning when it happened while the thread
800
+ loaded).
801
+ - Views nest stacks and lists at most 8 levels deep.
464
802
 
465
803
  The in-repository `AppServerClient` supports typed `request`, `onNotification`,
466
804
  `onRequest`, `readPayload`, `resolveTurn`, and `resolveItem` calls. Its