@kontextmind/kxm 0.7.178 → 0.7.179
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +11 -0
- package/docs/contributing/decisions.md +27 -1
- package/docs/reference/cli-reference.md +34 -5
- package/docs/reference/tools.md +6 -4
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/claude-hook.js +36 -45
- package/plugins/kxm/dist/cli.js +376 -259
- package/plugins/kxm/dist/extension.js +29 -37
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/plugins.ts +193 -25
- package/plugins/kxm/src/cli.ts +2 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/session-work.ts +34 -50
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,17 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
15
15
|
maps, roster, routes, gates, roles, and workflow definitions. Pi/OMP package
|
|
16
16
|
discovery now loads the tracked extension bundle, so the private TUI
|
|
17
17
|
workspace is bundled rather than becoming an unresolved runtime dependency.
|
|
18
|
+
- **OMP can opt into a focused KXM status band with live subscription limits.**
|
|
19
|
+
`kxm plugin install --omp --status-band` installs the shared extension and
|
|
20
|
+
selects an OMP custom layout with native active-subscription usage, metered
|
|
21
|
+
cost, context, model, mode, branch, pull request, and subagent signals. The KXM hook line
|
|
22
|
+
omits idle, zero-count, clean-ship, cost, and normal local-hub noise while
|
|
23
|
+
retaining degraded hub, active work, inbox, waiting, dirty/ahead, and update
|
|
24
|
+
signals. The work widget retains known metered cost and suppresses unknown
|
|
25
|
+
cost. The flag is explicit because it changes OMP user configuration; restart
|
|
26
|
+
OMP after applying it.
|
|
27
|
+
- Plugin installation now treats an interrupted harness process with no exit
|
|
28
|
+
status as a failure for Pi, OMP, and Claude Code instead of reporting success.
|
|
18
29
|
|
|
19
30
|
- **Hub project identity and its access key live in `.kxm/config.yaml`.**
|
|
20
31
|
`hub.local` and `hub.cloud` hold the URL, the project id to present, and a
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: "Decisions"
|
|
3
3
|
description: "Decisions of medium significance or higher made by automated KXM work in this repository. One entry per decision: the options evaluated with their weights, and why the selected option won. Recorded the turn the decision is taken."
|
|
4
4
|
audience: "agents and maintainers"
|
|
5
|
-
updated: "2026-10-
|
|
5
|
+
updated: "2026-10-11"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Decisions
|
|
@@ -172,3 +172,29 @@ Evidence: Evidence: merge-base 77beda4 predates the CLI restructure; merge-tree
|
|
|
172
172
|
- Selected: stash-apply onto a fresh lane off `origin/main`, take upstream
|
|
173
173
|
for claude-bridge, `dist`, and `package.json`, and merge the rest.
|
|
174
174
|
Evidence: Evidence: `git diff HEAD origin/main` on the stale branch showed upstream's claude-bridge fix and curl downloader already landed; the lane port keeps both those and the npm/`--all` behavior.
|
|
175
|
+
|
|
176
|
+
## 2026-10-11 — OMP subscription status integration (extension)
|
|
177
|
+
|
|
178
|
+
- Context: KXM's extension already owns a hook status line while OMP can
|
|
179
|
+
render OAuth subscription quota through its native cached `usage`
|
|
180
|
+
segment. Adding quota to KXM's line would duplicate OMP data; changing
|
|
181
|
+
the OMP layout during every plugin install would unexpectedly overwrite
|
|
182
|
+
an operator's chosen status line.
|
|
183
|
+
- Options:
|
|
184
|
+
- Run `omp usage` from the KXM extension — weight 0.1. Direct, but adds
|
|
185
|
+
process work to refreshes, duplicates OMP's cache and rendering, and
|
|
186
|
+
expands the credential-bearing boundary.
|
|
187
|
+
- Configure the native OMP segment on every install — weight 0.3.
|
|
188
|
+
Discoverable, but silently mutates unrelated user configuration.
|
|
189
|
+
- Add explicit `kxm plugin install --omp --status-band` opt-in and use
|
|
190
|
+
OMP's native `usage` segment — weight 0.9. No duplicate quota
|
|
191
|
+
implementation; configuration mutation is deliberate and verifiable.
|
|
192
|
+
- Selected: explicit opt-in. The installer snapshots the three global
|
|
193
|
+
status-line entries, writes the focused layout, verifies effective
|
|
194
|
+
values in the current working directory, restores prior values, and
|
|
195
|
+
resets entries that were previously absent or `null`.
|
|
196
|
+
- Solid recommendation: keep subscription quota in OMP's native `usage`
|
|
197
|
+
segment and KXM workflow state in the separate hook status line.
|
|
198
|
+
- Interim recommendation: use the opt-in installer until OMP exposes a
|
|
199
|
+
first-class transactional status-preset API.
|
|
200
|
+
Evidence: `plugins/kxm/src/cli/plugins.ts`, `plugins/kxm/src/session-work.ts`, and `test/core/cli.test.ts`.
|
|
@@ -13,7 +13,7 @@ Output shown under examples was captured from a source checkout, inside a throwa
|
|
|
13
13
|
- [Task to command](#task-to-command)
|
|
14
14
|
- Setup: [`init`](#kxm-init), [`config`](#kxm-config), [`completion`](#kxm-completion), [`trust`](#kxm-trust)
|
|
15
15
|
- Hub and sessions: [`hub`](#kxm-hub-view), [`session`](#kxm-session), [`dash`](#kxm-dash), [`studio`](#kxm-studio)
|
|
16
|
-
- Harnesses, models, and roles: [`harness`](#kxm-harness), [`auth`](#kxm-auth), [`update`](#kxm-update), [`models`](#kxm-models), [`routes`](#kxm-routes), [`role`](#kxm-role)
|
|
16
|
+
- Harnesses, models, and roles: [`harness`](#kxm-harness), [`plugin`](#kxm-plugin), [`auth`](#kxm-auth), [`update`](#kxm-update), [`models`](#kxm-models), [`routes`](#kxm-routes), [`role`](#kxm-role)
|
|
17
17
|
- Running work: [`lane`](#kxm-lane), [`land`](#kxm-land), [`assign`](#kxm-assign), [`run`](#kxm-run), [`runs`](#kxm-runs), [`docs`](#kxm-docs), [`runtime`](#kxm-runtime), [`agent`](#kxm-agent), [`workflow`](#kxm-workflow), [`gate`](#kxm-gate), [`peer`](#kxm-peer), [`task`](#kxm-task), [`goal`](#kxm-goal), [`suggest`](#kxm-suggest), [`explain`](#kxm-explain)
|
|
18
18
|
- Context and learning: [`context`](#kxm-context), [`memory`](#kxm-memory), [`skills`](#kxm-skills), [`improve`](#kxm-improve), [`routing`](#kxm-routing), [`prices`](#kxm-prices)
|
|
19
19
|
- Operations: [`backup`](#kxm-backup), [`restore`](#kxm-restore), [`tenant`](#kxm-tenant), [`ssh`](#kxm-ssh), [`help`](#kxm-help)
|
|
@@ -699,13 +699,14 @@ Prints recent tasks (workflow runs) and plans (journal entries) from the local h
|
|
|
699
699
|
- Side effects: on first use it mints a 24-hour operator session token and writes it to `<KXM_USER_CONFIG_DIR>/session.token` (mode 0600), and it caches the brief in `.kxm/state/session-brief.json`. Under `--dry-run` it still probes the hub and prints the brief, but it mints no token, writes neither file, and appends a `dry run: would write <file>` line for each (JSON: `dryRun` and `planned`); with no saved token the output carries no token. `--token --dry-run` plans only the token file.
|
|
700
700
|
- The default text output ends with the session token, and `--token` prints only the token. Treat the output as a credential.
|
|
701
701
|
- JSON (`kxm.session-brief.v1`) keys: `generatedAt`, `staleSeconds`, `source`, `hub` (`state`, `evidence`, `online`, `url`, `scope`), `stats`, `tasks`, `plans`, `statusLine`, `widgetLines`, `sessionToken`.
|
|
702
|
+
- The status line omits cost because harness-native bands already own that signal. The work widget includes known metered cost and suppresses unknown cost.
|
|
702
703
|
|
|
703
704
|
```bash
|
|
704
705
|
kxm session brief
|
|
705
706
|
```
|
|
706
707
|
|
|
707
708
|
```text
|
|
708
|
-
kxm
|
|
709
|
+
kxm · dirty
|
|
709
710
|
|
|
710
711
|
No recent tasks or plans.
|
|
711
712
|
|
|
@@ -717,7 +718,7 @@ kxm session brief --status
|
|
|
717
718
|
```
|
|
718
719
|
|
|
719
720
|
```text
|
|
720
|
-
kxm
|
|
721
|
+
kxm
|
|
721
722
|
```
|
|
722
723
|
|
|
723
724
|
```bash
|
|
@@ -725,7 +726,7 @@ kxm session brief --json
|
|
|
725
726
|
```
|
|
726
727
|
|
|
727
728
|
```text
|
|
728
|
-
{"schema":"kxm.session-brief.v1","generatedAt":"2026-09-23T13:49:55.253Z","staleSeconds":5,"source":"legacy","hub":{"state":"off","evidence":"probed","online":false,"url":"http://127.0.0.1:59998","scope":"loopback"},"stats":{"activeTasks":0,"waitingTasks":0,"planCount":0,"inbox":0,"runTotal":0},"tasks":[],"plans":[],"statusLine":"kxm hub:off
|
|
729
|
+
{"schema":"kxm.session-brief.v1","generatedAt":"2026-09-23T13:49:55.253Z","staleSeconds":5,"source":"legacy","hub":{"state":"off","evidence":"probed","online":false,"url":"http://127.0.0.1:59998","scope":"loopback"},"stats":{"activeTasks":0,"waitingTasks":0,"planCount":0,"inbox":0,"runTotal":0},"tasks":[],"plans":[],"statusLine":"kxm · hub:off",...,"sessionToken":"<token>"}
|
|
729
730
|
```
|
|
730
731
|
|
|
731
732
|
Before the first token exists:
|
|
@@ -735,7 +736,7 @@ kxm session brief --status --dry-run
|
|
|
735
736
|
```
|
|
736
737
|
|
|
737
738
|
```text
|
|
738
|
-
kxm
|
|
739
|
+
kxm · dirty
|
|
739
740
|
dry run: would write ~/.config/kxm/session.token
|
|
740
741
|
dry run: would write /work/proj/.kxm/state/session-brief.json
|
|
741
742
|
```
|
|
@@ -952,6 +953,34 @@ kxm harness list --json
|
|
|
952
953
|
{"schema":"kxm.cli-result.v1","ok":true,"command":"harness list","defaultHarness":"pi","harnesses":[{"id":"pi","label":"Pi","default":true,"mode":"headless","detected":false,"authenticated":false,"dispatch":{"status":"no","supported":false,"reason":"not_detected"},"canUpdate":{"self":true,"extensions":true,"models":true},"issues":[]},...]}
|
|
953
954
|
```
|
|
954
955
|
|
|
956
|
+
## `kxm plugin`
|
|
957
|
+
|
|
958
|
+
### `kxm plugin install`
|
|
959
|
+
|
|
960
|
+
```text
|
|
961
|
+
kxm plugin install [--all | --pi | --omp | --claude] [--status-band]
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
Installs the KXM extension into every discovered supported harness, or only the explicitly selected harnesses. `--status-band` is an explicit OMP-only opt-in: after installing the extension, it switches OMP to a focused custom status layout with model, mode, branch, pull request, subagent, active-subscription usage, metered cost, and context signals. It uses OMP's native background usage fetch and does not call `omp usage`. To support safe rollback, KXM parses OMP's global config file and retains only the three existing `statusLine` entries for the duration of the command; it does not use credential values.
|
|
965
|
+
|
|
966
|
+
| Option | Argument | Default | Description |
|
|
967
|
+
|---|---|---|---|
|
|
968
|
+
| `--all` | none | on when no harness flag is present | Install into every discovered supported harness |
|
|
969
|
+
| `--pi` | none | off | Install into Pi |
|
|
970
|
+
| `--omp` | none | off | Install into OMP |
|
|
971
|
+
| `--claude` | none | off | Install into Claude Code |
|
|
972
|
+
| `--status-band` | none | off | Replace OMP's status layout with the focused subscription layout |
|
|
973
|
+
|
|
974
|
+
- Mutates the selected harness installations. `--status-band` asks OMP for its global config directory, snapshots the three global status-line entries from `config.yml` or `config.yaml`, writes through `omp config set`, and verifies the effective values in the current working directory. On failure it restores prior values and resets entries that were previously absent or `null`. Restart OMP afterward.
|
|
975
|
+
- `--dry-run` reports the install and status-band intent without invoking a harness command.
|
|
976
|
+
- `--status-band` fails when OMP is not selected or detected. A failed or locally shadowed OMP configuration is reported as a failed OMP result rather than a successful install; verification covers the directory where the command runs, not every other project's OMP overrides.
|
|
977
|
+
|
|
978
|
+
```bash
|
|
979
|
+
kxm plugin install --omp --status-band
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
Not run in this reference: installs a plugin and changes OMP user configuration.
|
|
983
|
+
|
|
955
984
|
## `kxm auth`
|
|
956
985
|
|
|
957
986
|
### `kxm auth token`
|
package/docs/reference/tools.md
CHANGED
|
@@ -323,11 +323,11 @@ Proposes a change to one authoritative state key. Despite its name, it only prop
|
|
|
323
323
|
|
|
324
324
|
Returns `{ "proposalId" }`. Blocked by the `read-only` preset; under an attempt token it needs an explicit grant.
|
|
325
325
|
|
|
326
|
-
## Pi extension commands
|
|
326
|
+
## Pi and OMP extension commands
|
|
327
327
|
|
|
328
|
-
The Pi extension connects the session to the hub at start, registers the 19 tools, and adds two slash commands. It also keeps a `kxm` status line and `kxm-work` and `kxm-progress` widgets, refreshed after every turn.
|
|
328
|
+
The shared Pi-compatible extension connects the session to the hub at start, registers the 19 tools, and adds two slash commands. It also keeps a concise `kxm` status line and `kxm-work` and `kxm-progress` widgets, refreshed after every turn.
|
|
329
329
|
|
|
330
|
-
In Pi:
|
|
330
|
+
In Pi or OMP:
|
|
331
331
|
|
|
332
332
|
```text
|
|
333
333
|
/kxm
|
|
@@ -344,7 +344,9 @@ In Pi:
|
|
|
344
344
|
| `/kxm memory` | Shows the project memory brief by running `kxm memory brief` (or `$KXM_BIN`) |
|
|
345
345
|
| `/kxm help` | Lists the subcommands. Any unknown subcommand shows the same help |
|
|
346
346
|
|
|
347
|
-
The task picker, `Continue KXM work?`, lists recent tasks and plans and puts the chosen prompt in the editor. It also appears when a Pi TUI session starts, begins a new session or forks, unless `KXM_SESSION_BRIEF=off`.
|
|
347
|
+
The task picker, `Continue KXM work?`, lists recent tasks and plans and puts the chosen prompt in the editor. It also appears when a Pi or OMP TUI session starts, begins a new session or forks, unless `KXM_SESSION_BRIEF=off`.
|
|
348
|
+
|
|
349
|
+
OMP can also show the active OAuth subscription's quota windows in its native status band. Install the extension and opt into KXM's focused layout with `kxm plugin install --omp --status-band`, then restart OMP. The layout keeps model, mode, branch, pull request, subagent, subscription usage, metered cost, and context signals; it does not add OMP's inline `status` segment because OMP already renders the KXM hook status on its own line. If `statusLine.showHookStatus` is disabled, the native usage and cost segments remain but the separate KXM line stays hidden.
|
|
348
350
|
|
|
349
351
|
Inbound requests arrive as a displayed message that starts a model turn: `steer` at the next decision boundary, `followUp` (and `nextTurn`, which an unattended worker cannot wait on) after the current work. The final response is returned as the reply. If the model provider fails, the request stays `delivered` so a supervised worker can recover it. The extension also registers model providers; see [Harness routing](harness-routing.md) and [Nous providers](../guides/nous-providers.md).
|
|
350
352
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "kxm",
|
|
4
4
|
"displayName": "KXM",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.179",
|
|
6
6
|
"description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "KontextMind",
|
|
@@ -886,12 +886,12 @@ import { spawnSync } from "node:child_process";
|
|
|
886
886
|
import { randomUUID } from "node:crypto";
|
|
887
887
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
888
888
|
import { join as join4 } from "node:path";
|
|
889
|
-
function
|
|
889
|
+
function hubStatusPart(hub) {
|
|
890
890
|
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
891
|
-
if (hub?.state === "
|
|
892
|
-
if (hub?.state === "
|
|
893
|
-
if (hub?.
|
|
894
|
-
return
|
|
891
|
+
if (hub?.state === "off" || hub?.state === void 0 && hub?.online === false) return `hub:off${suffix}`;
|
|
892
|
+
if (hub?.state === "unknown") return `hub:unknown${suffix}`;
|
|
893
|
+
if (hub?.scope === "remote") return "hub:remote";
|
|
894
|
+
return void 0;
|
|
895
895
|
}
|
|
896
896
|
function truncate(value, width) {
|
|
897
897
|
const text = value.replace(/\s+/g, " ").trim();
|
|
@@ -969,17 +969,17 @@ function readGitShip(cwd) {
|
|
|
969
969
|
}
|
|
970
970
|
function formatCostLine(input) {
|
|
971
971
|
if (!input) return void 0;
|
|
972
|
-
if (input.sessionCostUsd === void 0 && input.runCostUsd === void 0) return
|
|
972
|
+
if (input.sessionCostUsd === void 0 && input.runCostUsd === void 0) return void 0;
|
|
973
973
|
const sess = input.sessionCostUsd !== void 0 ? `$${input.sessionCostUsd.toFixed(2)} sess` : void 0;
|
|
974
974
|
const run = input.runCostUsd !== void 0 ? `$${input.runCostUsd.toFixed(2)} run` : void 0;
|
|
975
975
|
const route = input.harness && input.model ? `${input.harness}/${input.model}` : input.model ?? void 0;
|
|
976
976
|
const parts = [sess, run, route].filter(Boolean);
|
|
977
|
-
return parts.length > 0 ? parts.join(" \xB7 ") :
|
|
977
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : void 0;
|
|
978
978
|
}
|
|
979
|
-
function renderStatusLine(statsOrBrief, current, hub, ship, updateLatest
|
|
979
|
+
function renderStatusLine(statsOrBrief, current, hub, ship, updateLatest) {
|
|
980
980
|
if ("schema" in statsOrBrief && statsOrBrief.schema === "kxm.session-brief.v1") {
|
|
981
981
|
const brief = statsOrBrief;
|
|
982
|
-
if (!current && !hub && !ship && !updateLatest
|
|
982
|
+
if (!current && !hub && !ship && !updateLatest) {
|
|
983
983
|
return brief.statusLine;
|
|
984
984
|
}
|
|
985
985
|
return formatSessionStatusLine(
|
|
@@ -987,46 +987,39 @@ function renderStatusLine(statsOrBrief, current, hub, ship, updateLatest, cost)
|
|
|
987
987
|
current,
|
|
988
988
|
hub ?? brief.hub,
|
|
989
989
|
ship,
|
|
990
|
-
updateLatest
|
|
991
|
-
cost ?? brief.cost
|
|
990
|
+
updateLatest
|
|
992
991
|
);
|
|
993
992
|
}
|
|
994
|
-
return formatSessionStatusLine(statsOrBrief, current, hub, ship, updateLatest
|
|
995
|
-
}
|
|
996
|
-
function formatSessionStatusLine(stats, current, hub, ship, updateLatest
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
return finalLine.length <= 80 ? finalLine : `${finalLine.slice(0, 79)}\u2026`;
|
|
1004
|
-
}
|
|
1005
|
-
const parts = [];
|
|
1006
|
-
if (current?.kind === "task") parts.push(`${head} ${current.detail}`);
|
|
1007
|
-
else if (current?.kind === "plan") parts.push(`${head} plan ${truncate(current.detail, 36)}`);
|
|
1008
|
-
else parts.push(head);
|
|
1009
|
-
parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
993
|
+
return formatSessionStatusLine(statsOrBrief, current, hub, ship, updateLatest);
|
|
994
|
+
}
|
|
995
|
+
function formatSessionStatusLine(stats, current, hub, ship, updateLatest) {
|
|
996
|
+
const parts = ["kxm"];
|
|
997
|
+
const hubPart = hubStatusPart(hub);
|
|
998
|
+
if (hubPart) parts.push(hubPart);
|
|
999
|
+
if (current?.kind === "task") parts.push(current.detail);
|
|
1000
|
+
else if (current?.kind === "plan") parts.push(`plan ${truncate(current.detail, 36)}`);
|
|
1001
|
+
if (stats.activeTasks > 0) parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
1010
1002
|
if (stats.waitingTasks > 0) parts.push(`${stats.waitingTasks} waiting`);
|
|
1011
|
-
parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
1003
|
+
if (stats.planCount > 0) parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
1012
1004
|
if (stats.inbox > 0) parts.push(`inbox ${stats.inbox}`);
|
|
1013
1005
|
if (ship?.dirty) parts.push("dirty");
|
|
1014
1006
|
else if (ship && ship.ahead > 0) parts.push(`${ship.ahead} local`);
|
|
1015
|
-
if (cost) parts.push(cost);
|
|
1016
1007
|
if (updateLatest) parts.push(`upd ${updateLatest}`);
|
|
1017
|
-
|
|
1018
|
-
return line.length <= 80 ? line : `${line.slice(0, 79)}\u2026`;
|
|
1008
|
+
return truncate(parts.join(" \xB7 "), 80);
|
|
1019
1009
|
}
|
|
1020
1010
|
function formatSessionWidget(stats, current, hub, ship, updateLatest, cost) {
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1011
|
+
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
1012
|
+
const hubMark = hub?.state === "on" || hub?.state === void 0 && hub?.online === true ? `hub:on${suffix}` : hub?.state === "off" || hub?.state === void 0 && hub?.online === false ? `hub:off${suffix}` : hub?.state === "unknown" ? `hub:unknown${suffix}` : void 0;
|
|
1013
|
+
const summary = ["KXM", hubMark];
|
|
1014
|
+
if (stats.activeTasks > 0) summary.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
1015
|
+
if (stats.waitingTasks > 0) summary.push(`${stats.waitingTasks} waiting`);
|
|
1016
|
+
if (stats.planCount > 0) summary.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
1017
|
+
if (stats.inbox > 0) summary.push(`inbox ${stats.inbox}`);
|
|
1018
|
+
const lines = [summary.filter(Boolean).join(" \xB7 ")];
|
|
1025
1019
|
if (current) lines.push(`now ${current.kind} ${truncate(current.detail, 56)}`);
|
|
1026
|
-
else if (stats.latestPlan) lines.push(`plan
|
|
1027
|
-
|
|
1028
|
-
lines.push(
|
|
1029
|
-
if (cost) lines.push(`cost ${cost}`);
|
|
1020
|
+
else if (stats.latestPlan) lines.push(`plan ${truncate(stats.latestPlan, 60)}`);
|
|
1021
|
+
if (ship?.dirty || ship && ship.ahead > 0) lines.push(formatShipLine(ship));
|
|
1022
|
+
if (cost && cost !== "unknown") lines.push(`cost ${cost}`);
|
|
1030
1023
|
if (updateLatest) lines.push(`update ${updateLatest} available \xB7 kxm update --kxm`);
|
|
1031
1024
|
return lines;
|
|
1032
1025
|
}
|
|
@@ -1046,7 +1039,7 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
|
|
|
1046
1039
|
const tasks = uniqueLabels(recentTasks(snapshot.runs).map(taskItem));
|
|
1047
1040
|
const plans = uniqueLabels(snapshot.plans.slice(0, MAX_SESSION_BRIEF_PLANS).map(planItem));
|
|
1048
1041
|
const resolvedHub = hub ?? { state: "off", evidence: "unconfigured", online: false };
|
|
1049
|
-
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest
|
|
1042
|
+
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest);
|
|
1050
1043
|
const widgetLines = formatSessionWidget(stats, current, resolvedHub, ship, updateLatest, cost);
|
|
1051
1044
|
return {
|
|
1052
1045
|
schema: SESSION_BRIEF_SCHEMA,
|
|
@@ -1110,7 +1103,7 @@ function estimateSessionCost(stateDir) {
|
|
|
1110
1103
|
latestModel = r.effectiveModel ?? r.requestedModel ?? latestModel;
|
|
1111
1104
|
latestHarness = r.harness ?? latestHarness;
|
|
1112
1105
|
}
|
|
1113
|
-
if (!hasMetered) return
|
|
1106
|
+
if (!hasMetered) return void 0;
|
|
1114
1107
|
const route = latestHarness && latestModel ? `${latestHarness}/${latestModel}` : latestModel;
|
|
1115
1108
|
return `$${totalCost.toFixed(2)} sess${route ? ` \xB7 ${route}` : ""}`;
|
|
1116
1109
|
} catch {
|
|
@@ -1147,8 +1140,7 @@ function loadSessionBrief(cwd, env = process.env, current, hub, options = {}) {
|
|
|
1147
1140
|
current,
|
|
1148
1141
|
effectiveHub,
|
|
1149
1142
|
options.ship,
|
|
1150
|
-
options.updateLatest
|
|
1151
|
-
effectiveCost
|
|
1143
|
+
options.updateLatest
|
|
1152
1144
|
);
|
|
1153
1145
|
const widgetLines = formatSessionWidget(
|
|
1154
1146
|
cached.stats,
|
|
@@ -1213,8 +1205,7 @@ async function loadSessionBriefAsync(cwd, env = process.env, current, hub, optio
|
|
|
1213
1205
|
current,
|
|
1214
1206
|
effectiveHub,
|
|
1215
1207
|
options.ship,
|
|
1216
|
-
void 0
|
|
1217
|
-
effectiveCost
|
|
1208
|
+
void 0
|
|
1218
1209
|
);
|
|
1219
1210
|
const widgetLines = formatSessionWidget(
|
|
1220
1211
|
cached.stats,
|