@tekmidian/pai 0.40.1 → 0.42.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/README.md +1 -0
- package/dist/aibroker-client-BP74gGwD.mjs +215 -0
- package/dist/aibroker-client-BP74gGwD.mjs.map +1 -0
- package/dist/aibroker-client-C6m71p6O.mjs +3 -0
- package/dist/{auto-route-BwzmlW3W.mjs → auto-route-_5zzJz5q.mjs} +2 -2
- package/dist/{auto-route-BwzmlW3W.mjs.map → auto-route-_5zzJz5q.mjs.map} +1 -1
- package/dist/browser-mcp/index.d.mts +1 -0
- package/dist/browser-mcp/index.mjs +352 -0
- package/dist/browser-mcp/index.mjs.map +1 -0
- package/dist/{chain-DhVHVnmT.mjs → chain-DGXNoTwc.mjs} +93 -320
- package/dist/chain-DGXNoTwc.mjs.map +1 -0
- package/dist/cli/index.mjs +12 -10
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +12 -10
- package/dist/{clusters-DRHhfli9.mjs → clusters-j0ZuNYp9.mjs} +1 -1
- package/dist/{clusters-DRHhfli9.mjs.map → clusters-j0ZuNYp9.mjs.map} +1 -1
- package/dist/{config-D1G9IFpn.mjs → config-B64vFg14.mjs} +3 -50
- package/dist/config-B64vFg14.mjs.map +1 -0
- package/dist/{config-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
- package/dist/{config-Dl8lT4Lu.mjs → config-DgzR5sfb.mjs} +2 -2
- package/dist/{config-Dl8lT4Lu.mjs.map → config-DgzR5sfb.mjs.map} +1 -1
- package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache-dUnSZwaA.mjs} +4 -4
- package/dist/{context-handover-cache-C6_rs7JT.mjs.map → context-handover-cache-dUnSZwaA.mjs.map} +1 -1
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-CKBkhOsZ.mjs +23 -0
- package/dist/{daemon-CpdVUnl_.mjs → daemon-DsvP8KMy.mjs} +357 -27
- package/dist/daemon-DsvP8KMy.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +20 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/detector-C-7KRi42.mjs +5 -0
- package/dist/{detector-smpCm7ku.mjs → detector-C73Nxm94.mjs} +1 -1
- package/dist/{detector-smpCm7ku.mjs.map → detector-C73Nxm94.mjs.map} +1 -1
- package/dist/{factory-Bc5EyN54.mjs → factory-DEb9NmHJ.mjs} +7 -7
- package/dist/{factory-Bc5EyN54.mjs.map → factory-DEb9NmHJ.mjs.map} +1 -1
- package/dist/factory-Dbo8yNPi.mjs +3 -0
- package/dist/{fallback-CdXv-Np0.mjs → fallback-5UJO-RyX.mjs} +5 -4
- package/dist/{fallback-CdXv-Np0.mjs.map → fallback-5UJO-RyX.mjs.map} +1 -1
- package/dist/hooks/worker-status-line.mjs +9 -4
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +593 -0
- package/dist/hooks/worker-supervision.mjs.map +7 -0
- package/dist/{indexer-backend-rohVVKrA.mjs → indexer-backend-BPTzKFZU.mjs} +1 -1
- package/dist/{indexer-backend-rohVVKrA.mjs.map → indexer-backend-BPTzKFZU.mjs.map} +1 -1
- package/dist/{ipc-client-C7AVpsyv.mjs → ipc-client-BmypMNYk.mjs} +2 -2
- package/dist/{ipc-client-C7AVpsyv.mjs.map → ipc-client-BmypMNYk.mjs.map} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
- package/dist/{latent-ideas-iNp5s55R.mjs → latent-ideas-CtSca4qG.mjs} +2 -2
- package/dist/{latent-ideas-iNp5s55R.mjs.map → latent-ideas-CtSca4qG.mjs.map} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs → link-boost-Bucnz3n8.mjs} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs.map → link-boost-Bucnz3n8.mjs.map} +1 -1
- package/dist/{main-resolver-CM1IHbuu.mjs → main-resolver-BgIrC-D9.mjs} +4 -214
- package/dist/main-resolver-BgIrC-D9.mjs.map +1 -0
- package/dist/main-resolver-DXGuygFG.mjs +7 -0
- package/dist/{neighborhood-AGL7oXQ2.mjs → neighborhood-CM7Jgb51.mjs} +1 -1
- package/dist/{neighborhood-AGL7oXQ2.mjs.map → neighborhood-CM7Jgb51.mjs.map} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs → note-context-DCwE2A-M.mjs} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs.map → note-context-DCwE2A-M.mjs.map} +1 -1
- package/dist/{planner-Cm3g6fWH.mjs → planner-BLDJt3ZS.mjs} +6 -5
- package/dist/{planner-Cm3g6fWH.mjs.map → planner-BLDJt3ZS.mjs.map} +1 -1
- package/dist/{postgres-Dwxe290H.mjs → postgres-CtOzfoQU.mjs} +1 -1
- package/dist/{postgres-Dwxe290H.mjs.map → postgres-CtOzfoQU.mjs.map} +1 -1
- package/dist/{program-AR0qlYRg.mjs → program-CruRnLtc.mjs} +99 -84
- package/dist/program-CruRnLtc.mjs.map +1 -0
- package/dist/query-feedback-DGoVRaWD.mjs +3 -0
- package/dist/{query-feedback-DZfjI34w.mjs → query-feedback-Dqz-HC__.mjs} +1 -1
- package/dist/{query-feedback-DZfjI34w.mjs.map → query-feedback-Dqz-HC__.mjs.map} +1 -1
- package/dist/router-BTIGZR2_.mjs +3 -0
- package/dist/{router-DHracTUC.mjs → router-DD6D4j31.mjs} +1 -1
- package/dist/{router-DHracTUC.mjs.map → router-DD6D4j31.mjs.map} +1 -1
- package/dist/runtime-paths-rni52zHX.mjs +50 -0
- package/dist/runtime-paths-rni52zHX.mjs.map +1 -0
- package/dist/scope-NIwCGxRI.mjs +351 -0
- package/dist/scope-NIwCGxRI.mjs.map +1 -0
- package/dist/{server-BOuAOj9b.mjs → server-Cp7tNzNC.mjs} +19 -2
- package/dist/server-Cp7tNzNC.mjs.map +1 -0
- package/dist/skills/Worker/SKILL.md +13 -0
- package/dist/{sources-ju6AA7lL.mjs → sources-CS43CpuS.mjs} +1 -1
- package/dist/{sources-ju6AA7lL.mjs.map → sources-CS43CpuS.mjs.map} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs → sqlite-Cq3gJcq9.mjs} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs.map → sqlite-Cq3gJcq9.mjs.map} +1 -1
- package/dist/{state-ahFAbD8b.mjs → state-BTa10SOM.mjs} +1 -1
- package/dist/{state-ahFAbD8b.mjs.map → state-BTa10SOM.mjs.map} +1 -1
- package/dist/{state-DH_mp5Xe.mjs → state-RXepvbnO.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs → themes-B7h9YDiy.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs.map → themes-B7h9YDiy.mjs.map} +1 -1
- package/dist/{tools-COna9vZR.mjs → tools-BW3NLMPk.mjs} +1 -1
- package/dist/{tools-CMlHa9lZ.mjs → tools-LlzcV_eK.mjs} +14 -14
- package/dist/{tools-CMlHa9lZ.mjs.map → tools-LlzcV_eK.mjs.map} +1 -1
- package/dist/{trace-DTP6k4B_.mjs → trace-u4hT-pLD.mjs} +1 -1
- package/dist/{trace-DTP6k4B_.mjs.map → trace-u4hT-pLD.mjs.map} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs → vault-indexer-mata9S7b.mjs} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs.map → vault-indexer-mata9S7b.mjs.map} +1 -1
- package/dist/{work-queue-worker-B3nl8yyn.mjs → work-queue-worker-7Vkli4Yh.mjs} +4 -4
- package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-7Vkli4Yh.mjs.map} +1 -1
- package/dist/work-queue-worker-D1p2V-ae.mjs +14 -0
- package/dist/{zettelkasten-DDvKRm2V.mjs → zettelkasten-F0D_XfQ4.mjs} +3 -3
- package/dist/{zettelkasten-DDvKRm2V.mjs.map → zettelkasten-F0D_XfQ4.mjs.map} +1 -1
- package/docs/commands/README.md +2 -2
- package/docs/commands/mcp.md +4 -4
- package/docs/commands/worker.md +3 -0
- package/docs/worker.md +40 -0
- package/package.json +1 -1
- package/plugins/productivity/hooks/hooks.json +5 -0
- package/src/hooks/ts/user-prompt/worker-supervision.ts +123 -0
- package/statusline-command.sh +65 -73
- package/dist/chain-DhVHVnmT.mjs.map +0 -1
- package/dist/config-D1G9IFpn.mjs.map +0 -1
- package/dist/daemon-BSL9sXxm.mjs +0 -22
- package/dist/daemon-CpdVUnl_.mjs.map +0 -1
- package/dist/detector-BLVOCiJ_.mjs +0 -5
- package/dist/factory-C7ua_3ik.mjs +0 -3
- package/dist/main-resolver-CM1IHbuu.mjs.map +0 -1
- package/dist/main-resolver-E5WlilhM.mjs +0 -6
- package/dist/program-AR0qlYRg.mjs.map +0 -1
- package/dist/query-feedback-CGcEiGtY.mjs +0 -3
- package/dist/router-CAtyVQud.mjs +0 -3
- package/dist/server-BOuAOj9b.mjs.map +0 -1
- package/dist/work-queue-worker-BM1GcAAq.mjs +0 -14
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* worker-supervision.ts
|
|
5
|
+
*
|
|
6
|
+
* UserPromptSubmit hook that surfaces worker supervision events into the
|
|
7
|
+
* session that owns the workers. The PAI daemon watches every worker's
|
|
8
|
+
* status file (src/workers/supervision.ts) and appends one JSON line per
|
|
9
|
+
* finished/failed/stalled event to
|
|
10
|
+
* <logDir>/supervision/<claude-session-id>.events. AIBroker pushes the same
|
|
11
|
+
* line into the terminal when it can; this hook is the channel that works
|
|
12
|
+
* without one — on the next prompt, every line the session has not yet seen
|
|
13
|
+
* is injected as context, and the cursor moves past it.
|
|
14
|
+
*
|
|
15
|
+
* Together the two channels replace polling: the orchestrator gets told its
|
|
16
|
+
* worker finished, failed or stalled, and never sleeps to find out.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { isWorkerSession } from "../lib/worker-session.js";
|
|
22
|
+
import { readWorkersSection } from "../../../workers/config.js";
|
|
23
|
+
import { workersLogDir } from "../../../workers/paths.js";
|
|
24
|
+
|
|
25
|
+
interface HookInput {
|
|
26
|
+
session_id?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Lines this session has already had injected. The count (not a byte offset)
|
|
31
|
+
* is the cursor because the file is append-only by construction; a recreated
|
|
32
|
+
* file simply clamps back to zero and replays its fresh content.
|
|
33
|
+
*/
|
|
34
|
+
function readCursor(path: string): number {
|
|
35
|
+
try {
|
|
36
|
+
return Math.max(0, Number(readFileSync(path, "utf8").trim()) || 0);
|
|
37
|
+
} catch {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function main(): void {
|
|
43
|
+
if (isWorkerSession()) return; // workers supervise nothing
|
|
44
|
+
|
|
45
|
+
let raw = "";
|
|
46
|
+
try {
|
|
47
|
+
raw = readFileSync(0, "utf8");
|
|
48
|
+
} catch {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!raw.trim()) return;
|
|
52
|
+
let input: HookInput;
|
|
53
|
+
try {
|
|
54
|
+
input = JSON.parse(raw) as HookInput;
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const session = input.session_id;
|
|
59
|
+
if (!session) return;
|
|
60
|
+
|
|
61
|
+
// logDir is wherever workers.logDir says (default ~/.claude/logs/workers);
|
|
62
|
+
// a missing or broken workers section means no supervision to surface
|
|
63
|
+
let logDir: string;
|
|
64
|
+
try {
|
|
65
|
+
logDir = workersLogDir(readWorkersSection().workers);
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const dir = join(logDir, "supervision");
|
|
71
|
+
const eventsPath = join(dir, `${session}.events`);
|
|
72
|
+
const cursorPath = join(dir, `${session}.cursor`);
|
|
73
|
+
if (!existsSync(eventsPath)) return;
|
|
74
|
+
|
|
75
|
+
let lines: string[];
|
|
76
|
+
try {
|
|
77
|
+
lines = readFileSync(eventsPath, "utf8").split("\n").filter((l) => l.trim());
|
|
78
|
+
} catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const fresh = lines.slice(readCursor(cursorPath));
|
|
82
|
+
if (fresh.length === 0) return;
|
|
83
|
+
|
|
84
|
+
// events AIBroker already typed into this terminal have a push receipt;
|
|
85
|
+
// this hook is the fallback channel and must not repeat them
|
|
86
|
+
let pushedIds = new Set<string>();
|
|
87
|
+
try {
|
|
88
|
+
pushedIds = new Set(
|
|
89
|
+
readFileSync(join(dir, `${session}.pushed`), "utf8")
|
|
90
|
+
.split("\n")
|
|
91
|
+
.map((l) => l.trim())
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
);
|
|
94
|
+
} catch {
|
|
95
|
+
// no receipts means nothing was pushed, everything surfaces here
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const texts = fresh
|
|
99
|
+
.map((l) => {
|
|
100
|
+
try {
|
|
101
|
+
const ev = JSON.parse(l) as { id?: string; text?: string };
|
|
102
|
+
if (ev.id && pushedIds.has(ev.id)) return null;
|
|
103
|
+
return ev.text ?? null;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
.filter((t): t is string => t !== null);
|
|
109
|
+
try {
|
|
110
|
+
writeFileSync(cursorPath, String(lines.length), "utf8");
|
|
111
|
+
} catch {
|
|
112
|
+
// an unwritable cursor only means these lines surface once more
|
|
113
|
+
}
|
|
114
|
+
if (texts.length === 0) return; // damaged lines are consumed, not re-shown
|
|
115
|
+
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
`<system-reminder>\nWORKER EVENTS (delivered by the daemon's supervision — no polling needed):\n` +
|
|
118
|
+
texts.join("\n") +
|
|
119
|
+
`\n</system-reminder>\n`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main();
|
package/statusline-command.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
#
|
|
12
12
|
# LINES DISPLAYED:
|
|
13
13
|
# 1. Greeting: DA name, model, directory
|
|
14
|
-
# 2. MCPs:
|
|
14
|
+
# 2. MCPs: server count, then names that fit the width (rest shown as +N)
|
|
15
15
|
# 3. Context: Current session context window usage (K / 200K)
|
|
16
16
|
#
|
|
17
17
|
# ENVIRONMENT VARIABLES (set in settings.json env section):
|
|
@@ -86,27 +86,26 @@ fi
|
|
|
86
86
|
# Config directory
|
|
87
87
|
claude_dir="${PAI_DIR:-$HOME/.claude}"
|
|
88
88
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
# Collect MCP server names from all config sources (settings.json, .mcp.json,
|
|
90
|
+
# ~/.claude.json). Only entries that define a server (object with a command,
|
|
91
|
+
# type or url field) count: Claude Code also stores per-tool usage stats in
|
|
92
|
+
# mcpServers under tool-name keys ("Read", "mcp__pai__memory_search", …), and
|
|
93
|
+
# those must never reach the status line. Keys are newline-separated so names
|
|
94
|
+
# containing spaces survive.
|
|
95
|
+
mcp_names=""
|
|
92
96
|
|
|
93
|
-
# Helper: merge MCP names from a jq-compatible JSON file
|
|
94
97
|
_merge_mcps() {
|
|
95
98
|
local file="$1"
|
|
96
99
|
[ -f "$file" ] || return
|
|
97
|
-
local
|
|
98
|
-
|
|
99
|
-
[ -n "$data" ] && [ "$data" != "null" ] || return
|
|
100
|
-
local names count
|
|
101
|
-
names=$(echo "$data" | head -1)
|
|
102
|
-
count=$(echo "$data" | tail -1)
|
|
100
|
+
local names
|
|
101
|
+
names=$(jq -r '.mcpServers // {} | to_entries[] | select(((.value | type) == "object") and (.value | (has("command") or has("type") or has("url")))) | .key' "$file" 2>/dev/null)
|
|
103
102
|
[ -n "$names" ] || return
|
|
104
|
-
if [ -n "$
|
|
105
|
-
|
|
103
|
+
if [ -n "$mcp_names" ]; then
|
|
104
|
+
mcp_names="${mcp_names}
|
|
105
|
+
${names}"
|
|
106
106
|
else
|
|
107
|
-
|
|
107
|
+
mcp_names="$names"
|
|
108
108
|
fi
|
|
109
|
-
mcps_count=$((mcps_count + count))
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
# Read from all three MCP config locations
|
|
@@ -114,11 +113,12 @@ _merge_mcps "$claude_dir/settings.json" # legacy
|
|
|
114
113
|
_merge_mcps "$claude_dir/.mcp.json" # project-level
|
|
115
114
|
_merge_mcps "$HOME/.claude.json" # user-level (e.g. Coogle, DEVONthink)
|
|
116
115
|
|
|
117
|
-
# Deduplicate
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
# Deduplicate (case-insensitive — macOS config keys drift in case), first
|
|
117
|
+
# spelling wins, order preserved
|
|
118
|
+
mcp_list=()
|
|
119
|
+
while IFS= read -r mcp_line; do
|
|
120
|
+
mcp_list+=("$mcp_line")
|
|
121
|
+
done < <(printf '%s\n' "$mcp_names" | awk 'NF && !seen[tolower($0)]++')
|
|
122
122
|
|
|
123
123
|
# Extract context window usage from Claude Code's JSON input (no JSONL parsing needed)
|
|
124
124
|
context_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' 2>/dev/null)
|
|
@@ -247,8 +247,7 @@ if [ "${PAI_SIMPLE_COLORS:-0}" = "1" ]; then
|
|
|
247
247
|
MCP_DEFAULT='\033[34m'
|
|
248
248
|
fi
|
|
249
249
|
|
|
250
|
-
# Format MCP
|
|
251
|
-
# Debug: log available width info (remove after testing)
|
|
250
|
+
# Format the MCP segment (see the render block below)
|
|
252
251
|
# Terminal width for line truncation
|
|
253
252
|
# Claude Code's statusline subprocess can't detect resize (stty returns stale values).
|
|
254
253
|
# To set your width: echo 105 > ~/.claude/.statusline_width
|
|
@@ -258,8 +257,7 @@ term_width=80
|
|
|
258
257
|
[ "$term_width" -gt 0 ] 2>/dev/null || term_width=80
|
|
259
258
|
mcp_prefix_width=10 # visual width of "🔌 MCPs: " (emoji=2 + space + "MCPs: " = 10)
|
|
260
259
|
|
|
261
|
-
#
|
|
262
|
-
# No width detection needed: if total display chars > 60, split at the midpoint.
|
|
260
|
+
# Display-name mapping for known servers
|
|
263
261
|
_mcp_display_name() {
|
|
264
262
|
case "$1" in
|
|
265
263
|
"daemon") echo "Daemon" ;;
|
|
@@ -275,7 +273,9 @@ _mcp_display_name() {
|
|
|
275
273
|
"macos_automator") echo "macOS" ;;
|
|
276
274
|
"claude_ai_Gmail") echo "Gmail" ;;
|
|
277
275
|
"claude_ai_Google_Calendar") echo "GCal" ;;
|
|
278
|
-
|
|
276
|
+
# capitalize without bash-4 ${n^} so /bin/bash 3.2 renders names too
|
|
277
|
+
*) local n="$1"
|
|
278
|
+
printf '%s%s' "$(printf '%s' "$n" | cut -c1 | tr '[:lower:]' '[:upper:]')" "$(printf '%s' "$n" | cut -c2-)" ;;
|
|
279
279
|
esac
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -288,57 +288,53 @@ _mcp_formatted() {
|
|
|
288
288
|
esac
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
#
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
for mcp in $mcp_names_raw; do
|
|
298
|
-
dn=$(_mcp_display_name "$mcp")
|
|
299
|
-
fm=$(_mcp_formatted "$dn")
|
|
300
|
-
mcp_display_names+=("$dn")
|
|
301
|
-
mcp_formatted_strs+=("$fm")
|
|
302
|
-
if [ $total_mcps -gt 0 ]; then
|
|
303
|
-
total_display_width=$((total_display_width + 2)) # ", "
|
|
304
|
-
fi
|
|
305
|
-
total_display_width=$((total_display_width + ${#dn}))
|
|
306
|
-
total_mcps=$((total_mcps + 1))
|
|
307
|
-
done
|
|
308
|
-
|
|
309
|
-
# Decide: one line or two lines?
|
|
310
|
-
# If total display width > 60 chars, split at the midpoint
|
|
291
|
+
# Build the MCP segment: "N: name1, name2, … +K" — total server count first,
|
|
292
|
+
# then as many display names as fit the width (hard cap regardless of width),
|
|
293
|
+
# the rest collapsed into "+K". Always a single line: what doesn't fit is
|
|
294
|
+
# counted, never printed.
|
|
295
|
+
mcp_total=${#mcp_list[@]}
|
|
311
296
|
mcp_line1=""
|
|
312
|
-
mcp_line2=""
|
|
313
297
|
|
|
314
|
-
if [ $
|
|
298
|
+
if [ "$mcp_total" -eq 0 ]; then
|
|
315
299
|
mcp_line1="none"
|
|
316
|
-
elif [ $total_display_width -le $term_width ]; then
|
|
317
|
-
# Single line — everything fits
|
|
318
|
-
for ((i=0; i<total_mcps; i++)); do
|
|
319
|
-
if [ $i -eq 0 ]; then
|
|
320
|
-
mcp_line1="${mcp_formatted_strs[$i]}"
|
|
321
|
-
else
|
|
322
|
-
mcp_line1="${mcp_line1}${SEPARATOR_COLOR}, ${mcp_formatted_strs[$i]}"
|
|
323
|
-
fi
|
|
324
|
-
done
|
|
325
300
|
else
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
for
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
301
|
+
mcp_max_names=6
|
|
302
|
+
# Width budget for the name list: terminal minus prefix, minus the leading
|
|
303
|
+
# "N: " and a reserve for the trailing " +K" overflow marker
|
|
304
|
+
mcp_budget=$(( term_width - mcp_prefix_width - ${#mcp_total} - 2 - 6 ))
|
|
305
|
+
[ "$mcp_budget" -lt 12 ] && mcp_budget=12
|
|
306
|
+
mcp_width=0
|
|
307
|
+
mcp_shown=0
|
|
308
|
+
mcp_overflow=0
|
|
309
|
+
for mcp in "${mcp_list[@]}"; do
|
|
310
|
+
dn=$(_mcp_display_name "$mcp")
|
|
311
|
+
w_add=${#dn}
|
|
312
|
+
[ "$mcp_shown" -gt 0 ] && w_add=$(( w_add + 2 )) # ", " separator
|
|
313
|
+
if [ "$mcp_shown" -ge "$mcp_max_names" ] || [ $(( mcp_width + w_add )) -gt "$mcp_budget" ]; then
|
|
314
|
+
mcp_overflow=$(( mcp_overflow + 1 ))
|
|
315
|
+
continue
|
|
333
316
|
fi
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
317
|
+
fm=$(_mcp_formatted "$dn")
|
|
318
|
+
if [ "$mcp_shown" -eq 0 ]; then
|
|
319
|
+
mcp_line1="${mcp_total}${SEPARATOR_COLOR}: ${RESET}${fm}"
|
|
320
|
+
mcp_width=${#mcp_total}
|
|
338
321
|
else
|
|
339
|
-
|
|
322
|
+
mcp_line1="${mcp_line1}${SEPARATOR_COLOR}, ${fm}"
|
|
323
|
+
mcp_width=$(( mcp_width + 2 ))
|
|
340
324
|
fi
|
|
325
|
+
mcp_width=$(( mcp_width + ${#dn} ))
|
|
326
|
+
mcp_shown=$(( mcp_shown + 1 ))
|
|
341
327
|
done
|
|
328
|
+
if [ "$mcp_shown" -eq 0 ]; then
|
|
329
|
+
# Even the first name exceeds the budget — truncate that one name
|
|
330
|
+
dn=$(_mcp_display_name "${mcp_list[0]}")
|
|
331
|
+
avail=$(( mcp_budget - 1 ))
|
|
332
|
+
[ "$avail" -lt 3 ] && avail=3
|
|
333
|
+
dn="${dn:0:$((avail - 1))}…"
|
|
334
|
+
mcp_line1="${mcp_total}${SEPARATOR_COLOR}: ${RESET}$(_mcp_formatted "$dn")"
|
|
335
|
+
mcp_overflow=$(( mcp_total - 1 ))
|
|
336
|
+
fi
|
|
337
|
+
[ "$mcp_overflow" -gt 0 ] && mcp_line1="${mcp_line1}${SEPARATOR_COLOR} +${RESET}${LINE2_ACCENT}${mcp_overflow}${RESET}"
|
|
342
338
|
fi
|
|
343
339
|
|
|
344
340
|
# Output the statusline
|
|
@@ -356,12 +352,8 @@ else
|
|
|
356
352
|
printf "${line1_short}\n"
|
|
357
353
|
fi
|
|
358
354
|
|
|
359
|
-
# LINE 2 - MCPs (
|
|
355
|
+
# LINE 2 - MCPs (single line, capped by width; overflow shown as +N)
|
|
360
356
|
printf "${LINE2_PRIMARY}${EMOJI_PLUG} MCPs${RESET}${LINE2_PRIMARY}${SEPARATOR_COLOR}: ${RESET}${mcp_line1}${RESET}\n"
|
|
361
|
-
if [ -n "$mcp_line2" ]; then
|
|
362
|
-
# Continuation line — indent to align with MCP names after "🔌 MCPs: "
|
|
363
|
-
printf "${LINE2_PRIMARY} ${RESET}${mcp_line2}${RESET}\n"
|
|
364
|
-
fi
|
|
365
357
|
|
|
366
358
|
|
|
367
359
|
# Usage suffix: provider-aware. glm sessions read the Z.ai plan quota
|