mu-harness 0.16.19 → 0.16.21
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/esm/tui/chat/ChatApp.js +1 -1
- package/esm/tui/chat/picker.d.ts +1 -2
- package/esm/tui/chat/picker.js +87 -22
- package/esm/tui/chat/transcript.js +1 -1
- package/package.json +3 -3
- package/script/tui/chat/ChatApp.js +1 -1
- package/script/tui/chat/picker.d.ts +1 -2
- package/script/tui/chat/picker.js +87 -23
- package/script/tui/chat/transcript.js +1 -1
package/esm/tui/chat/ChatApp.js
CHANGED
|
@@ -1159,7 +1159,7 @@ export class ChatApp {
|
|
|
1159
1159
|
children.push(listView(rows, this.paletteCursor, this.theme()));
|
|
1160
1160
|
}
|
|
1161
1161
|
else if (this.pickerVisible()) {
|
|
1162
|
-
const rows = this.pickerRanked.map((c) => ({ left: c.label, right: c.kind === '
|
|
1162
|
+
const rows = this.pickerRanked.map((c) => ({ left: c.label, right: c.kind === 'file' ? '' : c.kind }));
|
|
1163
1163
|
children.push(listView(rows, this.pickerCursor, this.theme()));
|
|
1164
1164
|
}
|
|
1165
1165
|
const waiting = this.waitingView();
|
package/esm/tui/chat/picker.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
export interface Candidate {
|
|
2
2
|
label: string;
|
|
3
3
|
insert: string;
|
|
4
|
-
kind: 'file' | 'agent';
|
|
4
|
+
kind: 'file' | 'dir' | 'agent';
|
|
5
5
|
}
|
|
6
6
|
export declare function collectCandidates(cwd: string, agentNames: string[]): Candidate[];
|
|
7
|
-
export declare function invalidateCandidates(): void;
|
|
8
7
|
export declare function rank(query: string, candidates: Candidate[], limit?: number): Candidate[];
|
|
9
8
|
export interface ActiveMention {
|
|
10
9
|
start: number;
|
package/esm/tui/chat/picker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import { readdirSync, statSync } from 'node:fs';
|
|
2
3
|
import { join, relative } from 'node:path';
|
|
3
4
|
const IGNORED = new Set([
|
|
@@ -19,11 +20,26 @@ const IGNORED = new Set([
|
|
|
19
20
|
]);
|
|
20
21
|
const MAX_ENTRIES = 5000;
|
|
21
22
|
const MAX_DEPTH = 6;
|
|
22
|
-
|
|
23
|
+
function gitFiles(cwd) {
|
|
24
|
+
try {
|
|
25
|
+
const out = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {
|
|
26
|
+
cwd,
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
29
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
30
|
+
});
|
|
31
|
+
const files = out.split('\0').filter((f) => f.length > 0);
|
|
32
|
+
return files.length > 0 ? files.slice(0, MAX_ENTRIES).sort() : undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
23
38
|
function walk(cwd) {
|
|
24
|
-
const
|
|
39
|
+
const files = [];
|
|
40
|
+
const dirs = [];
|
|
25
41
|
const stack = [{ dir: cwd, depth: 0 }];
|
|
26
|
-
while (stack.length > 0 &&
|
|
42
|
+
while (stack.length > 0 && files.length < MAX_ENTRIES) {
|
|
27
43
|
const { dir, depth } = stack.pop();
|
|
28
44
|
let entries;
|
|
29
45
|
try {
|
|
@@ -46,43 +62,65 @@ function walk(cwd) {
|
|
|
46
62
|
continue;
|
|
47
63
|
}
|
|
48
64
|
if (isDir) {
|
|
65
|
+
dirs.push(relative(cwd, full));
|
|
49
66
|
if (depth < MAX_DEPTH)
|
|
50
67
|
stack.push({ dir: full, depth: depth + 1 });
|
|
51
68
|
}
|
|
52
69
|
else {
|
|
53
|
-
|
|
54
|
-
if (
|
|
70
|
+
files.push(relative(cwd, full));
|
|
71
|
+
if (files.length >= MAX_ENTRIES)
|
|
55
72
|
break;
|
|
56
73
|
}
|
|
57
74
|
}
|
|
58
75
|
}
|
|
59
|
-
return
|
|
76
|
+
return { files: files.sort(), dirs: dirs.sort() };
|
|
60
77
|
}
|
|
61
78
|
export function collectCandidates(cwd, agentNames) {
|
|
62
|
-
if (!cache || cache.cwd !== cwd)
|
|
63
|
-
cache = { cwd, files: walk(cwd) };
|
|
64
79
|
const agents = agentNames.map((name) => ({ label: `@${name}`, insert: name, kind: 'agent' }));
|
|
65
|
-
const
|
|
66
|
-
|
|
80
|
+
const tracked = gitFiles(cwd);
|
|
81
|
+
const walked = walk(cwd);
|
|
82
|
+
const paths = tracked ?? walked.files;
|
|
83
|
+
const dirs = new Set(walked.dirs);
|
|
84
|
+
for (const path of paths) {
|
|
85
|
+
for (let i = 0; i < path.length; i++) {
|
|
86
|
+
if (path[i] === '/' || path[i] === '\\')
|
|
87
|
+
dirs.add(path.slice(0, i));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const dirCandidates = [...dirs].map((path) => ({ label: `${path}/`, insert: `${path}/`, kind: 'dir' }));
|
|
91
|
+
const files = paths.map((path) => ({ label: path, insert: path, kind: 'file' }));
|
|
92
|
+
return [...agents, ...dirCandidates, ...files];
|
|
67
93
|
}
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
function isBoundary(target, i) {
|
|
95
|
+
if (i === 0)
|
|
96
|
+
return true;
|
|
97
|
+
const prev = target[i - 1] ?? '';
|
|
98
|
+
if (/[/\\_\-. ]/.test(prev))
|
|
99
|
+
return true;
|
|
100
|
+
const cur = target[i] ?? '';
|
|
101
|
+
return prev === prev.toLowerCase() && prev !== prev.toUpperCase() && cur === cur.toUpperCase() &&
|
|
102
|
+
cur !== cur.toLowerCase();
|
|
70
103
|
}
|
|
71
|
-
function
|
|
104
|
+
function fuzzyScore(query, target) {
|
|
72
105
|
if (query.length === 0)
|
|
73
106
|
return 0;
|
|
74
107
|
const q = query.toLowerCase();
|
|
75
|
-
const t = target.toLowerCase();
|
|
76
108
|
let qi = 0;
|
|
77
109
|
let total = 0;
|
|
78
110
|
let prev = -2;
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
let run = 0;
|
|
112
|
+
for (let i = 0; i < target.length && qi < q.length; i++) {
|
|
113
|
+
if (target[i].toLowerCase() === q[qi]) {
|
|
81
114
|
let s = 1;
|
|
82
|
-
if (i === prev + 1)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
if (i === prev + 1) {
|
|
116
|
+
run += 1;
|
|
117
|
+
s += 2 + run;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
run = 0;
|
|
121
|
+
}
|
|
122
|
+
if (isBoundary(target, i))
|
|
123
|
+
s += 4;
|
|
86
124
|
total += s;
|
|
87
125
|
prev = i;
|
|
88
126
|
qi += 1;
|
|
@@ -90,14 +128,41 @@ function score(query, target) {
|
|
|
90
128
|
}
|
|
91
129
|
if (qi < q.length)
|
|
92
130
|
return undefined;
|
|
93
|
-
return total
|
|
131
|
+
return total;
|
|
132
|
+
}
|
|
133
|
+
function basename(path) {
|
|
134
|
+
const p = path.endsWith('/') || path.endsWith('\\') ? path.slice(0, -1) : path;
|
|
135
|
+
const cut = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
|
|
136
|
+
return cut === -1 ? p : p.slice(cut + 1);
|
|
137
|
+
}
|
|
138
|
+
function score(query, candidate) {
|
|
139
|
+
if (query.length === 0)
|
|
140
|
+
return 0;
|
|
141
|
+
const pathScore = fuzzyScore(query, candidate.label);
|
|
142
|
+
if (pathScore === undefined)
|
|
143
|
+
return undefined;
|
|
144
|
+
let total = pathScore;
|
|
145
|
+
if (candidate.kind === 'file' || candidate.kind === 'dir') {
|
|
146
|
+
const base = basename(candidate.label);
|
|
147
|
+
const baseScore = fuzzyScore(query, base);
|
|
148
|
+
if (baseScore !== undefined) {
|
|
149
|
+
total += baseScore * 2;
|
|
150
|
+
const lb = base.toLowerCase();
|
|
151
|
+
const lq = query.toLowerCase();
|
|
152
|
+
if (lb === lq)
|
|
153
|
+
total += 100;
|
|
154
|
+
else if (lb.startsWith(lq))
|
|
155
|
+
total += 40;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return total - candidate.label.length * 0.05;
|
|
94
159
|
}
|
|
95
160
|
export function rank(query, candidates, limit = 8) {
|
|
96
161
|
if (!query)
|
|
97
162
|
return candidates.slice(0, limit);
|
|
98
163
|
const scored = [];
|
|
99
164
|
for (const candidate of candidates) {
|
|
100
|
-
const s = score(query, candidate
|
|
165
|
+
const s = score(query, candidate);
|
|
101
166
|
if (s !== undefined)
|
|
102
167
|
scored.push({ candidate, score: s });
|
|
103
168
|
}
|
|
@@ -176,7 +176,7 @@ export function formatToolArgs(name, input, max = 120) {
|
|
|
176
176
|
if (input === null || typeof input !== 'object')
|
|
177
177
|
return truncateText(String(input ?? ''), max);
|
|
178
178
|
const args = input;
|
|
179
|
-
if (name === 'edit' || name === 'write' || name === 'read' || name === '
|
|
179
|
+
if (name === 'edit' || name === 'write' || name === 'read' || name === 'list') {
|
|
180
180
|
return truncateText(stringifyArg(args.path), max);
|
|
181
181
|
}
|
|
182
182
|
if (name === 'bash')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mu-harness",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.21",
|
|
4
4
|
"description": "Agent harness: createHarness wires mu-core into a host — XDG paths, model registry, plugins, disk-loaded agents & skills, sub-agents, sessions (JSONL + SQLite catalog), slash commands, permission/approval hooks, an optional scheduler, and a composable TUI chat app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./script/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"@swc/wasm-typescript": "^1.15.0",
|
|
24
24
|
"cli-highlight": "^2.1.11",
|
|
25
25
|
"croner": "^9.0.0",
|
|
26
|
-
"mu-core": "^0.16.
|
|
27
|
-
"mu-tui": "^0.16.
|
|
26
|
+
"mu-core": "^0.16.21",
|
|
27
|
+
"mu-tui": "^0.16.21"
|
|
28
28
|
},
|
|
29
29
|
"_generatedBy": "dnt@dev",
|
|
30
30
|
"types": "./esm/index.d.ts"
|
|
@@ -1162,7 +1162,7 @@ class ChatApp {
|
|
|
1162
1162
|
children.push(listView(rows, this.paletteCursor, this.theme()));
|
|
1163
1163
|
}
|
|
1164
1164
|
else if (this.pickerVisible()) {
|
|
1165
|
-
const rows = this.pickerRanked.map((c) => ({ left: c.label, right: c.kind === '
|
|
1165
|
+
const rows = this.pickerRanked.map((c) => ({ left: c.label, right: c.kind === 'file' ? '' : c.kind }));
|
|
1166
1166
|
children.push(listView(rows, this.pickerCursor, this.theme()));
|
|
1167
1167
|
}
|
|
1168
1168
|
const waiting = this.waitingView();
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
export interface Candidate {
|
|
2
2
|
label: string;
|
|
3
3
|
insert: string;
|
|
4
|
-
kind: 'file' | 'agent';
|
|
4
|
+
kind: 'file' | 'dir' | 'agent';
|
|
5
5
|
}
|
|
6
6
|
export declare function collectCandidates(cwd: string, agentNames: string[]): Candidate[];
|
|
7
|
-
export declare function invalidateCandidates(): void;
|
|
8
7
|
export declare function rank(query: string, candidates: Candidate[], limit?: number): Candidate[];
|
|
9
8
|
export interface ActiveMention {
|
|
10
9
|
start: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.collectCandidates = collectCandidates;
|
|
4
|
-
exports.invalidateCandidates = invalidateCandidates;
|
|
5
4
|
exports.rank = rank;
|
|
6
5
|
exports.activeMention = activeMention;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
7
|
const node_fs_1 = require("node:fs");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
9
|
const IGNORED = new Set([
|
|
@@ -25,11 +25,26 @@ const IGNORED = new Set([
|
|
|
25
25
|
]);
|
|
26
26
|
const MAX_ENTRIES = 5000;
|
|
27
27
|
const MAX_DEPTH = 6;
|
|
28
|
-
|
|
28
|
+
function gitFiles(cwd) {
|
|
29
|
+
try {
|
|
30
|
+
const out = (0, node_child_process_1.execFileSync)('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {
|
|
31
|
+
cwd,
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
34
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
35
|
+
});
|
|
36
|
+
const files = out.split('\0').filter((f) => f.length > 0);
|
|
37
|
+
return files.length > 0 ? files.slice(0, MAX_ENTRIES).sort() : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
29
43
|
function walk(cwd) {
|
|
30
|
-
const
|
|
44
|
+
const files = [];
|
|
45
|
+
const dirs = [];
|
|
31
46
|
const stack = [{ dir: cwd, depth: 0 }];
|
|
32
|
-
while (stack.length > 0 &&
|
|
47
|
+
while (stack.length > 0 && files.length < MAX_ENTRIES) {
|
|
33
48
|
const { dir, depth } = stack.pop();
|
|
34
49
|
let entries;
|
|
35
50
|
try {
|
|
@@ -52,43 +67,65 @@ function walk(cwd) {
|
|
|
52
67
|
continue;
|
|
53
68
|
}
|
|
54
69
|
if (isDir) {
|
|
70
|
+
dirs.push((0, node_path_1.relative)(cwd, full));
|
|
55
71
|
if (depth < MAX_DEPTH)
|
|
56
72
|
stack.push({ dir: full, depth: depth + 1 });
|
|
57
73
|
}
|
|
58
74
|
else {
|
|
59
|
-
|
|
60
|
-
if (
|
|
75
|
+
files.push((0, node_path_1.relative)(cwd, full));
|
|
76
|
+
if (files.length >= MAX_ENTRIES)
|
|
61
77
|
break;
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
}
|
|
65
|
-
return
|
|
81
|
+
return { files: files.sort(), dirs: dirs.sort() };
|
|
66
82
|
}
|
|
67
83
|
function collectCandidates(cwd, agentNames) {
|
|
68
|
-
if (!cache || cache.cwd !== cwd)
|
|
69
|
-
cache = { cwd, files: walk(cwd) };
|
|
70
84
|
const agents = agentNames.map((name) => ({ label: `@${name}`, insert: name, kind: 'agent' }));
|
|
71
|
-
const
|
|
72
|
-
|
|
85
|
+
const tracked = gitFiles(cwd);
|
|
86
|
+
const walked = walk(cwd);
|
|
87
|
+
const paths = tracked ?? walked.files;
|
|
88
|
+
const dirs = new Set(walked.dirs);
|
|
89
|
+
for (const path of paths) {
|
|
90
|
+
for (let i = 0; i < path.length; i++) {
|
|
91
|
+
if (path[i] === '/' || path[i] === '\\')
|
|
92
|
+
dirs.add(path.slice(0, i));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const dirCandidates = [...dirs].map((path) => ({ label: `${path}/`, insert: `${path}/`, kind: 'dir' }));
|
|
96
|
+
const files = paths.map((path) => ({ label: path, insert: path, kind: 'file' }));
|
|
97
|
+
return [...agents, ...dirCandidates, ...files];
|
|
73
98
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
99
|
+
function isBoundary(target, i) {
|
|
100
|
+
if (i === 0)
|
|
101
|
+
return true;
|
|
102
|
+
const prev = target[i - 1] ?? '';
|
|
103
|
+
if (/[/\\_\-. ]/.test(prev))
|
|
104
|
+
return true;
|
|
105
|
+
const cur = target[i] ?? '';
|
|
106
|
+
return prev === prev.toLowerCase() && prev !== prev.toUpperCase() && cur === cur.toUpperCase() &&
|
|
107
|
+
cur !== cur.toLowerCase();
|
|
76
108
|
}
|
|
77
|
-
function
|
|
109
|
+
function fuzzyScore(query, target) {
|
|
78
110
|
if (query.length === 0)
|
|
79
111
|
return 0;
|
|
80
112
|
const q = query.toLowerCase();
|
|
81
|
-
const t = target.toLowerCase();
|
|
82
113
|
let qi = 0;
|
|
83
114
|
let total = 0;
|
|
84
115
|
let prev = -2;
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
let run = 0;
|
|
117
|
+
for (let i = 0; i < target.length && qi < q.length; i++) {
|
|
118
|
+
if (target[i].toLowerCase() === q[qi]) {
|
|
87
119
|
let s = 1;
|
|
88
|
-
if (i === prev + 1)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
if (i === prev + 1) {
|
|
121
|
+
run += 1;
|
|
122
|
+
s += 2 + run;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
run = 0;
|
|
126
|
+
}
|
|
127
|
+
if (isBoundary(target, i))
|
|
128
|
+
s += 4;
|
|
92
129
|
total += s;
|
|
93
130
|
prev = i;
|
|
94
131
|
qi += 1;
|
|
@@ -96,14 +133,41 @@ function score(query, target) {
|
|
|
96
133
|
}
|
|
97
134
|
if (qi < q.length)
|
|
98
135
|
return undefined;
|
|
99
|
-
return total
|
|
136
|
+
return total;
|
|
137
|
+
}
|
|
138
|
+
function basename(path) {
|
|
139
|
+
const p = path.endsWith('/') || path.endsWith('\\') ? path.slice(0, -1) : path;
|
|
140
|
+
const cut = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
|
|
141
|
+
return cut === -1 ? p : p.slice(cut + 1);
|
|
142
|
+
}
|
|
143
|
+
function score(query, candidate) {
|
|
144
|
+
if (query.length === 0)
|
|
145
|
+
return 0;
|
|
146
|
+
const pathScore = fuzzyScore(query, candidate.label);
|
|
147
|
+
if (pathScore === undefined)
|
|
148
|
+
return undefined;
|
|
149
|
+
let total = pathScore;
|
|
150
|
+
if (candidate.kind === 'file' || candidate.kind === 'dir') {
|
|
151
|
+
const base = basename(candidate.label);
|
|
152
|
+
const baseScore = fuzzyScore(query, base);
|
|
153
|
+
if (baseScore !== undefined) {
|
|
154
|
+
total += baseScore * 2;
|
|
155
|
+
const lb = base.toLowerCase();
|
|
156
|
+
const lq = query.toLowerCase();
|
|
157
|
+
if (lb === lq)
|
|
158
|
+
total += 100;
|
|
159
|
+
else if (lb.startsWith(lq))
|
|
160
|
+
total += 40;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return total - candidate.label.length * 0.05;
|
|
100
164
|
}
|
|
101
165
|
function rank(query, candidates, limit = 8) {
|
|
102
166
|
if (!query)
|
|
103
167
|
return candidates.slice(0, limit);
|
|
104
168
|
const scored = [];
|
|
105
169
|
for (const candidate of candidates) {
|
|
106
|
-
const s = score(query, candidate
|
|
170
|
+
const s = score(query, candidate);
|
|
107
171
|
if (s !== undefined)
|
|
108
172
|
scored.push({ candidate, score: s });
|
|
109
173
|
}
|
|
@@ -183,7 +183,7 @@ function formatToolArgs(name, input, max = 120) {
|
|
|
183
183
|
if (input === null || typeof input !== 'object')
|
|
184
184
|
return truncateText(String(input ?? ''), max);
|
|
185
185
|
const args = input;
|
|
186
|
-
if (name === 'edit' || name === 'write' || name === 'read' || name === '
|
|
186
|
+
if (name === 'edit' || name === 'write' || name === 'read' || name === 'list') {
|
|
187
187
|
return truncateText(stringifyArg(args.path), max);
|
|
188
188
|
}
|
|
189
189
|
if (name === 'bash')
|