@wangzhizhi/remi 0.1.225 → 0.1.244
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 +206 -2
- package/dist/help.js +1 -1
- package/dist/initPrompt.js +1 -1
- package/dist/setup.js +5 -4
- package/dist/statusCard.js +1 -1
- package/dist/statusline.js +1 -1
- package/dist/tui/RemiApp.js +55 -7
- package/dist/tui/hooksPanel.js +2 -2
- package/dist/tui/renderers/MessageList.js +10 -7
- package/dist/version.js +1 -1
- package/node_modules/@remi/compact/dist/index.js +13 -5
- package/node_modules/@remi/compact/package.json +1 -1
- package/node_modules/@remi/config/dist/index.js +816 -38
- package/node_modules/@remi/config/package.json +1 -1
- package/node_modules/@remi/core/dist/cacheBenchmark.js +217 -0
- package/node_modules/@remi/core/dist/cacheDiagnostics.js +300 -0
- package/node_modules/@remi/core/dist/contextBuilder.js +13 -11
- package/node_modules/@remi/core/dist/index.js +630 -33
- package/node_modules/@remi/core/dist/stableHash.js +30 -0
- package/node_modules/@remi/core/package.json +1 -1
- package/node_modules/@remi/hooks/package.json +1 -1
- package/node_modules/@remi/llm/package.json +1 -1
- package/node_modules/@remi/mcp/dist/index.js +32 -18
- package/node_modules/@remi/mcp/package.json +1 -1
- package/node_modules/@remi/memory/package.json +1 -1
- package/node_modules/@remi/permissions/package.json +1 -1
- package/node_modules/@remi/sessions/dist/index.js +38 -0
- package/node_modules/@remi/sessions/package.json +1 -1
- package/node_modules/@remi/skills/dist/index.js +2 -2
- package/node_modules/@remi/skills/package.json +1 -1
- package/node_modules/@remi/terminal-markdown/dist/index.js +159 -22
- package/node_modules/@remi/terminal-markdown/package.json +1 -1
- package/node_modules/@remi/tools/dist/index.js +1 -1
- package/node_modules/@remi/tools/package.json +1 -1
- package/package.json +13 -13
- package/prompt/tool-use-system.md +19 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export function stableHash(value) {
|
|
3
|
+
return createHash('sha256').update(stableStringify(value)).digest('hex').slice(0, 16);
|
|
4
|
+
}
|
|
5
|
+
export function stableJson(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.map(item => stableJson(item));
|
|
8
|
+
}
|
|
9
|
+
if (!value || typeof value !== 'object') {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
const record = value;
|
|
13
|
+
return Object.fromEntries(Object.keys(record)
|
|
14
|
+
.sort()
|
|
15
|
+
.filter(key => record[key] !== undefined)
|
|
16
|
+
.map(key => [key, stableJson(record[key])]));
|
|
17
|
+
}
|
|
18
|
+
export function stableStringify(value) {
|
|
19
|
+
if (value === null || typeof value !== 'object') {
|
|
20
|
+
return JSON.stringify(value) ?? 'undefined';
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return `[${value.map(item => stableStringify(item)).join(',')}]`;
|
|
24
|
+
}
|
|
25
|
+
const record = value;
|
|
26
|
+
return `{${Object.keys(record)
|
|
27
|
+
.sort()
|
|
28
|
+
.map(key => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
|
|
29
|
+
.join(',')}}`;
|
|
30
|
+
}
|
|
@@ -186,12 +186,13 @@ export async function createMcpRuntime(config, options, baseExecutor) {
|
|
|
186
186
|
await client.close();
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
const
|
|
189
|
+
const sortedTools = sortDiscoveredMcpTools(tools);
|
|
190
|
+
const registry = createToolRegistry(sortedTools.map(tool => tool.definition));
|
|
190
191
|
return {
|
|
191
|
-
tools,
|
|
192
|
+
tools: sortedTools,
|
|
192
193
|
diagnostics,
|
|
193
194
|
registry,
|
|
194
|
-
executor: createMcpToolExecutor(
|
|
195
|
+
executor: createMcpToolExecutor(sortedTools, baseExecutor),
|
|
195
196
|
close: async () => {
|
|
196
197
|
await Promise.all(sessions.map(session => session.close()));
|
|
197
198
|
},
|
|
@@ -199,7 +200,7 @@ export async function createMcpRuntime(config, options, baseExecutor) {
|
|
|
199
200
|
}
|
|
200
201
|
export function createMcpAwareToolRegistry(base, mcpTools) {
|
|
201
202
|
const registry = createToolRegistry(base.list());
|
|
202
|
-
for (const tool of mcpTools) {
|
|
203
|
+
for (const tool of sortDiscoveredMcpTools(mcpTools)) {
|
|
203
204
|
registry.register(tool.definition);
|
|
204
205
|
}
|
|
205
206
|
return registry;
|
|
@@ -221,6 +222,7 @@ export function createMcpToolExecutor(mcpTools, baseExecutor) {
|
|
|
221
222
|
}
|
|
222
223
|
async function discoverToolsForClient(serverName, client, usedNames) {
|
|
223
224
|
const tools = [];
|
|
225
|
+
const remoteTools = [];
|
|
224
226
|
let cursor;
|
|
225
227
|
for (let page = 0; page < maxMcpListPages; page += 1) {
|
|
226
228
|
const result = await client.listToolsPage(cursor);
|
|
@@ -229,25 +231,28 @@ async function discoverToolsForClient(serverName, client, usedNames) {
|
|
|
229
231
|
if (!remoteTool) {
|
|
230
232
|
continue;
|
|
231
233
|
}
|
|
232
|
-
|
|
233
|
-
const definition = createMcpToolDefinition(serverName, remoteTool.name, remiName, remoteTool);
|
|
234
|
-
tools.push({
|
|
235
|
-
serverName,
|
|
236
|
-
remoteName: remoteTool.name,
|
|
237
|
-
remiName,
|
|
238
|
-
description: remoteTool.description,
|
|
239
|
-
inputSchema: definition.inputSchema,
|
|
240
|
-
...(remoteTool.rawInputSchema ? { rawInputSchema: remoteTool.rawInputSchema } : {}),
|
|
241
|
-
...(remoteTool.annotations ? { annotations: remoteTool.annotations } : {}),
|
|
242
|
-
client,
|
|
243
|
-
definition,
|
|
244
|
-
});
|
|
234
|
+
remoteTools.push(remoteTool);
|
|
245
235
|
}
|
|
246
236
|
cursor = result.nextCursor;
|
|
247
237
|
if (!cursor) {
|
|
248
238
|
break;
|
|
249
239
|
}
|
|
250
240
|
}
|
|
241
|
+
for (const remoteTool of remoteTools.sort(compareRemoteTools)) {
|
|
242
|
+
const remiName = uniqueToolName(mcpToolName(serverName, remoteTool.name), usedNames);
|
|
243
|
+
const definition = createMcpToolDefinition(serverName, remoteTool.name, remiName, remoteTool);
|
|
244
|
+
tools.push({
|
|
245
|
+
serverName,
|
|
246
|
+
remoteName: remoteTool.name,
|
|
247
|
+
remiName,
|
|
248
|
+
description: remoteTool.description,
|
|
249
|
+
inputSchema: definition.inputSchema,
|
|
250
|
+
...(remoteTool.rawInputSchema ? { rawInputSchema: remoteTool.rawInputSchema } : {}),
|
|
251
|
+
...(remoteTool.annotations ? { annotations: remoteTool.annotations } : {}),
|
|
252
|
+
client,
|
|
253
|
+
definition,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
251
256
|
return tools;
|
|
252
257
|
}
|
|
253
258
|
function normalizeRemoteTool(rawTool) {
|
|
@@ -384,7 +389,16 @@ function applyMcpPermissionMode(context, decision) {
|
|
|
384
389
|
function enabledMcpServers(config) {
|
|
385
390
|
return Object.entries(config?.servers ?? {})
|
|
386
391
|
.filter(([, server]) => server.enabled !== false)
|
|
387
|
-
.filter(([, server]) => server.type === undefined || server.type === 'stdio')
|
|
392
|
+
.filter(([, server]) => server.type === undefined || server.type === 'stdio')
|
|
393
|
+
.sort(([leftName], [rightName]) => leftName.localeCompare(rightName));
|
|
394
|
+
}
|
|
395
|
+
function sortDiscoveredMcpTools(tools) {
|
|
396
|
+
return [...tools].sort((left, right) => left.serverName.localeCompare(right.serverName) ||
|
|
397
|
+
left.remoteName.localeCompare(right.remoteName) ||
|
|
398
|
+
left.remiName.localeCompare(right.remiName));
|
|
399
|
+
}
|
|
400
|
+
function compareRemoteTools(left, right) {
|
|
401
|
+
return left.name.localeCompare(right.name) || left.description.localeCompare(right.description);
|
|
388
402
|
}
|
|
389
403
|
function mcpToolName(serverName, toolName) {
|
|
390
404
|
return `mcp__${safeToolNameSegment(serverName)}__${safeToolNameSegment(toolName)}`;
|
|
@@ -348,6 +348,23 @@ function isSessionEvent(value) {
|
|
|
348
348
|
if (event.type === 'system') {
|
|
349
349
|
return typeof event.content === 'string' && (event.level === 'info' || event.level === 'warn' || event.level === 'error');
|
|
350
350
|
}
|
|
351
|
+
if (event.type === 'skill_use') {
|
|
352
|
+
return (typeof event.name === 'string' &&
|
|
353
|
+
typeof event.displayName === 'string' &&
|
|
354
|
+
typeof event.source === 'string' &&
|
|
355
|
+
typeof event.filePath === 'string');
|
|
356
|
+
}
|
|
357
|
+
if (event.type === 'pending_action') {
|
|
358
|
+
return (typeof event.callId === 'string' &&
|
|
359
|
+
typeof event.sourceToolName === 'string' &&
|
|
360
|
+
optionalString(event.command) &&
|
|
361
|
+
optionalString(event.completionCommand) &&
|
|
362
|
+
optionalString(event.tokenName) &&
|
|
363
|
+
optionalString(event.token) &&
|
|
364
|
+
optionalString(event.url) &&
|
|
365
|
+
optionalString(event.qrImagePath) &&
|
|
366
|
+
optionalString(event.message));
|
|
367
|
+
}
|
|
351
368
|
if (event.type === 'assistant') {
|
|
352
369
|
return typeof event.content === 'string' && Boolean(event.model && typeof event.model === 'object');
|
|
353
370
|
}
|
|
@@ -368,6 +385,27 @@ function isSessionEvent(value) {
|
|
|
368
385
|
(event.trigger === undefined || event.trigger === 'manual' || event.trigger === 'auto' || event.trigger === 'reactive') &&
|
|
369
386
|
(event.strategy === undefined || event.strategy === 'deterministic' || event.strategy === 'model'));
|
|
370
387
|
}
|
|
388
|
+
if (event.type === 'cache_diagnostic') {
|
|
389
|
+
const request = event.request;
|
|
390
|
+
const usage = event.usage;
|
|
391
|
+
const inference = event.inference;
|
|
392
|
+
return (Boolean(event.model && typeof event.model === 'object') &&
|
|
393
|
+
Boolean(request && typeof request === 'object') &&
|
|
394
|
+
Boolean(usage && typeof usage === 'object') &&
|
|
395
|
+
Boolean(inference && typeof inference === 'object') &&
|
|
396
|
+
typeof request?.prefixHash === 'string' &&
|
|
397
|
+
typeof request.messageHash === 'string' &&
|
|
398
|
+
typeof request.toolSchemaHash === 'string' &&
|
|
399
|
+
typeof request.toolSchemaTokens === 'number' &&
|
|
400
|
+
Array.isArray(event.layers) &&
|
|
401
|
+
typeof usage?.inputTokens === 'number' &&
|
|
402
|
+
typeof usage.cachedInputTokens === 'number' &&
|
|
403
|
+
typeof usage.outputTokens === 'number' &&
|
|
404
|
+
typeof usage.totalTokens === 'number' &&
|
|
405
|
+
typeof usage.cacheHitRate === 'number' &&
|
|
406
|
+
inference?.inferred === true &&
|
|
407
|
+
typeof inference.missReason === 'string');
|
|
408
|
+
}
|
|
371
409
|
return false;
|
|
372
410
|
}
|
|
373
411
|
function isSessionArtifactRef(value) {
|
|
@@ -82,8 +82,8 @@ export function formatSkillIndexContext(index, loadedSkills = []) {
|
|
|
82
82
|
const lines = [
|
|
83
83
|
'## Remi Skills',
|
|
84
84
|
'',
|
|
85
|
-
'Progressive disclosure: this section lists discovered skills from `skills/<name>/SKILL.md`, `~/.remi/skills/<name>/SKILL.md`, and `~/.agents/skills/<name>/SKILL.md`. Only the skill index frontmatter is loaded by default; full skill instructions appear below
|
|
86
|
-
'When a listed skill semantically matches the user request, call `load_skill` with the skill name before reading files, editing files, running commands, or answering about the task. If the match is ambiguous, do not load a skill. Do not claim that a skill was used unless full instructions have been loaded.',
|
|
85
|
+
'Progressive disclosure: this section lists discovered skills from `skills/<name>/SKILL.md`, `~/.remi/skills/<name>/SKILL.md`, and `~/.agents/skills/<name>/SKILL.md`. Only the skill index frontmatter is loaded by default; full skill instructions appear below when the user explicitly selects a skill, the model loads it with the `load_skill` tool, or the current session already loaded it recently.',
|
|
86
|
+
'When a listed skill semantically matches the user request and its full instructions are not already present below, call `load_skill` with the skill name before reading files, editing files, running commands, or answering about the task. If the match is ambiguous, do not load a skill. Do not claim that a skill was used unless full instructions have been loaded.',
|
|
87
87
|
];
|
|
88
88
|
if (enabledSkills.length > 0) {
|
|
89
89
|
lines.push('', 'Available skill index:');
|
|
@@ -536,6 +536,9 @@ const defaultInlineStyle = {
|
|
|
536
536
|
italic: false,
|
|
537
537
|
underline: false,
|
|
538
538
|
};
|
|
539
|
+
const longUrlDisplayThreshold = 72;
|
|
540
|
+
const compactUrlDisplayMaxWidth = 64;
|
|
541
|
+
const fallbackUrlPattern = /https?:\/\/[^\s<>"']+/gi;
|
|
539
542
|
function InlineTokenView({ token, theme, style, }) {
|
|
540
543
|
if (token.type === 'codespan') {
|
|
541
544
|
return _jsx(StyledInlineText, { text: token.text ?? '', theme: theme, style: withInlineStyle(defaultInlineStyle, { color: theme.accent }) });
|
|
@@ -551,6 +554,11 @@ function InlineTokenView({ token, theme, style, }) {
|
|
|
551
554
|
}
|
|
552
555
|
if (token.type === 'link') {
|
|
553
556
|
const linkStyle = withInlineStyle(style, { color: theme.accent });
|
|
557
|
+
const linkText = inlineText(token.tokens, token.text ?? token.href ?? '');
|
|
558
|
+
const compactLinkText = terminalLinkText(linkText, token.href);
|
|
559
|
+
if (compactLinkText !== undefined) {
|
|
560
|
+
return _jsx(StyledInlineText, { text: compactLinkText, theme: theme, style: linkStyle });
|
|
561
|
+
}
|
|
554
562
|
if (token.tokens?.length) {
|
|
555
563
|
return _jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? token.href ?? '', theme: theme, style: linkStyle });
|
|
556
564
|
}
|
|
@@ -580,6 +588,42 @@ function withInlineStyle(style, patch) {
|
|
|
580
588
|
return next;
|
|
581
589
|
}
|
|
582
590
|
function splitFallbackInlineTokens(text) {
|
|
591
|
+
const urlTokens = splitFallbackUrlTokens(text);
|
|
592
|
+
if (urlTokens) {
|
|
593
|
+
return urlTokens;
|
|
594
|
+
}
|
|
595
|
+
return splitFallbackMarkerTokens(text);
|
|
596
|
+
}
|
|
597
|
+
function splitFallbackUrlTokens(text) {
|
|
598
|
+
const tokens = [];
|
|
599
|
+
let lastIndex = 0;
|
|
600
|
+
for (const match of text.matchAll(fallbackUrlPattern)) {
|
|
601
|
+
const rawMatch = match[0] ?? '';
|
|
602
|
+
const matchIndex = match.index ?? 0;
|
|
603
|
+
if (!rawMatch) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const { url, suffix } = splitUrlTrailingText(rawMatch);
|
|
607
|
+
if (!url) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
tokens.push(...splitFallbackMarkerTokens(text.slice(lastIndex, matchIndex)));
|
|
611
|
+
tokens.push({ type: 'link', text: url, href: url });
|
|
612
|
+
if (suffix) {
|
|
613
|
+
tokens.push(...splitFallbackMarkerTokens(suffix));
|
|
614
|
+
}
|
|
615
|
+
lastIndex = matchIndex + rawMatch.length;
|
|
616
|
+
}
|
|
617
|
+
if (tokens.length === 0) {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
tokens.push(...splitFallbackMarkerTokens(text.slice(lastIndex)));
|
|
621
|
+
return tokens.length > 0 ? tokens : undefined;
|
|
622
|
+
}
|
|
623
|
+
function splitFallbackMarkerTokens(text) {
|
|
624
|
+
if (text.length === 0) {
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
583
627
|
const tokens = [];
|
|
584
628
|
let index = 0;
|
|
585
629
|
let plainStart = 0;
|
|
@@ -607,6 +651,86 @@ function splitFallbackInlineTokens(text) {
|
|
|
607
651
|
flushPlain(text.length);
|
|
608
652
|
return tokens.length > 0 ? tokens : [{ type: 'text', text }];
|
|
609
653
|
}
|
|
654
|
+
function terminalLinkText(text, href) {
|
|
655
|
+
const linkHref = href?.trim();
|
|
656
|
+
const visibleText = text.trim();
|
|
657
|
+
if (!linkHref || !isLikelyUrl(linkHref) || !isRawUrlText(visibleText, linkHref)) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
const label = compactUrlForTerminalDisplay(linkHref);
|
|
661
|
+
return label === linkHref ? linkHref : terminalHyperlink(label, linkHref);
|
|
662
|
+
}
|
|
663
|
+
function isLikelyUrl(value) {
|
|
664
|
+
return /^https?:\/\/\S+$/i.test(value);
|
|
665
|
+
}
|
|
666
|
+
function isRawUrlText(text, href) {
|
|
667
|
+
if (!text) {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
const normalizedText = trimUrlTrailingText(text).url;
|
|
671
|
+
const normalizedHref = trimUrlTrailingText(href).url;
|
|
672
|
+
return normalizedText === normalizedHref || isLikelyUrl(normalizedText);
|
|
673
|
+
}
|
|
674
|
+
export function compactInlineUrlsForDisplay(text) {
|
|
675
|
+
return text.replace(fallbackUrlPattern, rawMatch => {
|
|
676
|
+
const { url, suffix } = splitUrlTrailingText(rawMatch);
|
|
677
|
+
if (!url) {
|
|
678
|
+
return rawMatch;
|
|
679
|
+
}
|
|
680
|
+
const label = compactUrlForTerminalDisplay(url);
|
|
681
|
+
return `${label === url ? url : terminalHyperlink(label, url)}${suffix}`;
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
export function compactUrlForTerminalDisplay(url, maxWidth = compactUrlDisplayMaxWidth) {
|
|
685
|
+
if (displayWidth(url) <= longUrlDisplayThreshold || displayWidth(url) <= maxWidth) {
|
|
686
|
+
return url;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const parsed = new URL(url);
|
|
690
|
+
const host = `${parsed.protocol}//${parsed.host}`;
|
|
691
|
+
const segments = parsed.pathname.split('/').filter(segment => segment.length > 0);
|
|
692
|
+
const lastSegment = segments.at(-1);
|
|
693
|
+
const path = lastSegment ? `/.../${lastSegment}` : '/...';
|
|
694
|
+
const queryKeys = Array.from(parsed.searchParams.keys()).slice(0, 2);
|
|
695
|
+
const query = queryKeys.length > 0 ? `?${queryKeys.join('&')}...` : '';
|
|
696
|
+
const structured = `${host}${path}${query}`;
|
|
697
|
+
if (displayWidth(structured) <= maxWidth) {
|
|
698
|
+
return structured;
|
|
699
|
+
}
|
|
700
|
+
return compactMiddle(url, maxWidth);
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
return compactMiddle(url, maxWidth);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function compactMiddle(value, maxWidth) {
|
|
707
|
+
if (displayWidth(value) <= maxWidth) {
|
|
708
|
+
return value;
|
|
709
|
+
}
|
|
710
|
+
const headLength = Math.max(12, Math.floor(maxWidth * 0.58));
|
|
711
|
+
const tailLength = Math.max(8, maxWidth - headLength - 3);
|
|
712
|
+
return `${value.slice(0, headLength)}...${value.slice(-tailLength)}`;
|
|
713
|
+
}
|
|
714
|
+
function terminalHyperlink(label, href) {
|
|
715
|
+
return `\u001B]8;;${escapeOsc8Uri(href)}\u0007${label}\u001B]8;;\u0007`;
|
|
716
|
+
}
|
|
717
|
+
function escapeOsc8Uri(value) {
|
|
718
|
+
return value.replace(/[\u0007\u001B]/g, '');
|
|
719
|
+
}
|
|
720
|
+
function splitUrlTrailingText(value) {
|
|
721
|
+
const trimmed = trimUrlTrailingText(value);
|
|
722
|
+
return {
|
|
723
|
+
url: trimmed.url,
|
|
724
|
+
suffix: value.slice(trimmed.url.length),
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function trimUrlTrailingText(value) {
|
|
728
|
+
let end = value.length;
|
|
729
|
+
while (end > 0 && /[),.;:!?,。;:!?)]/.test(value[end - 1] ?? '')) {
|
|
730
|
+
end -= 1;
|
|
731
|
+
}
|
|
732
|
+
return { url: value.slice(0, end) };
|
|
733
|
+
}
|
|
610
734
|
function inlineMarkerAt(text, index) {
|
|
611
735
|
if (text.startsWith('`', index)) {
|
|
612
736
|
return { raw: '`', type: 'codespan' };
|
|
@@ -1447,32 +1571,45 @@ export function displayWidth(value) {
|
|
|
1447
1571
|
return width;
|
|
1448
1572
|
}
|
|
1449
1573
|
function isZeroWidthCodePoint(codePoint) {
|
|
1450
|
-
return
|
|
1451
|
-
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
1452
|
-
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
1453
|
-
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
1454
|
-
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
1455
|
-
(codePoint >= 0xfe00 && codePoint <= 0xfe0f));
|
|
1574
|
+
return codePoint === 0x200d || isCodePointInRanges(codePoint, zeroWidthRanges);
|
|
1456
1575
|
}
|
|
1457
1576
|
function isEmojiCodePoint(codePoint) {
|
|
1458
|
-
return (
|
|
1459
|
-
(codePoint >= 0x2600 && codePoint <= 0x27bf));
|
|
1577
|
+
return isCodePointInRanges(codePoint, emojiRanges);
|
|
1460
1578
|
}
|
|
1461
1579
|
function isFullWidthCodePoint(codePoint) {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1580
|
+
if (codePoint < 0x1100) {
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
if (codePoint <= 0x115f || codePoint === 0x2329 || codePoint === 0x232a) {
|
|
1584
|
+
return true;
|
|
1585
|
+
}
|
|
1586
|
+
return codePoint !== 0x303f && isCodePointInRanges(codePoint, fullWidthRanges);
|
|
1587
|
+
}
|
|
1588
|
+
const zeroWidthRanges = [
|
|
1589
|
+
[0x0300, 0x036f],
|
|
1590
|
+
[0x1ab0, 0x1aff],
|
|
1591
|
+
[0x1dc0, 0x1dff],
|
|
1592
|
+
[0x20d0, 0x20ff],
|
|
1593
|
+
[0xfe00, 0xfe0f],
|
|
1594
|
+
];
|
|
1595
|
+
const emojiRanges = [
|
|
1596
|
+
[0x1f000, 0x1faff],
|
|
1597
|
+
[0x2600, 0x27bf],
|
|
1598
|
+
];
|
|
1599
|
+
const fullWidthRanges = [
|
|
1600
|
+
[0x2e80, 0xa4cf],
|
|
1601
|
+
[0xac00, 0xd7a3],
|
|
1602
|
+
[0xf900, 0xfaff],
|
|
1603
|
+
[0xfe10, 0xfe19],
|
|
1604
|
+
[0xfe30, 0xfe6f],
|
|
1605
|
+
[0xff00, 0xff60],
|
|
1606
|
+
[0xffe0, 0xffe6],
|
|
1607
|
+
[0x1f300, 0x1f64f],
|
|
1608
|
+
[0x1f900, 0x1f9ff],
|
|
1609
|
+
[0x20000, 0x3fffd],
|
|
1610
|
+
];
|
|
1611
|
+
function isCodePointInRanges(codePoint, ranges) {
|
|
1612
|
+
return ranges.some(([start, end]) => codePoint >= start && codePoint <= end);
|
|
1476
1613
|
}
|
|
1477
1614
|
export function flattenText(node) {
|
|
1478
1615
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
@@ -4056,7 +4056,7 @@ async function resolveReadablePath(inputPath, context, options = {}) {
|
|
|
4056
4056
|
throw toolError('INVALID_INPUT', 'Path must not be empty.');
|
|
4057
4057
|
}
|
|
4058
4058
|
if (looksLikeUrlPathInput(inputPath)) {
|
|
4059
|
-
throw toolError('PATH_IS_URL', `Filesystem tools cannot inspect URLs: ${inputPath}. Use web_fetch for public URLs, or load a relevant internal-platform skill for authenticated
|
|
4059
|
+
throw toolError('PATH_IS_URL', `Filesystem tools cannot inspect URLs: ${inputPath}. Use web_fetch for public URLs, or load a relevant internal-platform skill for authenticated private URLs.`);
|
|
4060
4060
|
}
|
|
4061
4061
|
const absolutePath = isAbsolute(inputPath) ? resolve(inputPath) : resolve(context.cwd, inputPath);
|
|
4062
4062
|
const realTarget = await realpath(absolutePath).catch(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wangzhizhi/remi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.244",
|
|
4
4
|
"description": "Remi AI coding agent CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
"node": ">=24 <25"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@remi/compact": "0.1.
|
|
14
|
-
"@remi/config": "0.1.
|
|
15
|
-
"@remi/core": "0.1.
|
|
16
|
-
"@remi/hooks": "0.1.
|
|
17
|
-
"@remi/llm": "0.1.
|
|
18
|
-
"@remi/memory": "0.1.
|
|
19
|
-
"@remi/mcp": "0.1.
|
|
20
|
-
"@remi/permissions": "0.1.
|
|
21
|
-
"@remi/sessions": "0.1.
|
|
22
|
-
"@remi/skills": "0.1.
|
|
23
|
-
"@remi/terminal-markdown": "0.1.
|
|
24
|
-
"@remi/tools": "0.1.
|
|
13
|
+
"@remi/compact": "0.1.244",
|
|
14
|
+
"@remi/config": "0.1.244",
|
|
15
|
+
"@remi/core": "0.1.244",
|
|
16
|
+
"@remi/hooks": "0.1.244",
|
|
17
|
+
"@remi/llm": "0.1.244",
|
|
18
|
+
"@remi/memory": "0.1.244",
|
|
19
|
+
"@remi/mcp": "0.1.244",
|
|
20
|
+
"@remi/permissions": "0.1.244",
|
|
21
|
+
"@remi/sessions": "0.1.244",
|
|
22
|
+
"@remi/skills": "0.1.244",
|
|
23
|
+
"@remi/terminal-markdown": "0.1.244",
|
|
24
|
+
"@remi/tools": "0.1.244",
|
|
25
25
|
"highlight.js": "11.11.1",
|
|
26
26
|
"ink": "7.0.4",
|
|
27
27
|
"marked": "17.0.1",
|
|
@@ -119,13 +119,29 @@
|
|
|
119
119
|
- If `write_file` reports `FILE_EXISTS`, read the existing file and use `edit_file` for a targeted change, or use `write_file` with `overwrite: true` only for an intentional full-file replacement.
|
|
120
120
|
- If `edit_file` or `write_file` reports `FILE_NOT_READ`, `FILE_PARTIALLY_READ`, or `FILE_CHANGED_SINCE_READ`, read the file again and retry only when the requested change is still clear.
|
|
121
121
|
|
|
122
|
-
#### 5.4.
|
|
122
|
+
#### 5.4. Remi Configuration Editing
|
|
123
|
+
|
|
124
|
+
- When the user asks to add, update, remove, or inspect Remi model profiles or hooks, treat it as a local configuration task. If the user provides enough information, inspect and edit the relevant `config.toml` instead of only explaining the format.
|
|
125
|
+
- Default to the user-level config. On macOS/Linux this is `<home>/.remi/config.toml`; on Windows this is `%USERPROFILE%\.remi\config.toml` or the home path shown in runtime context plus `\.remi\config.toml`.
|
|
126
|
+
- If `REMI_HOME` is explicitly known from the user or runtime context, use it as the Remi home: if it already ends with `.remi`, use `<REMI_HOME>/config.toml`; otherwise use `<REMI_HOME>/.remi/config.toml`.
|
|
127
|
+
- Use project-specific config only when the user explicitly asks for the setting to apply only to the current project. The project-specific path is under the Remi home at `projects/<project-id>/config.toml`, where `<project-id>` is the first 16 hex characters of `sha256(resolve(cwd))`.
|
|
128
|
+
- Create the config file and parent directory when they are missing. Preserve unrelated existing TOML sections and entries.
|
|
129
|
+
- For model profile requests, write `[[profiles]]` entries with `provider`, `model_id`, `display_name`, `base_url`, `api_key` or `api_key_env`, and `context_window`. Use `model_id` as both the profile name and provider model id unless the user explicitly requests a separate name.
|
|
130
|
+
- Do not write `max_output_tokens`, `supports_tools`, `supports_reasoning`, or `effort` unless the user explicitly asks to override Remi defaults. The default effort level is `max`.
|
|
131
|
+
- If the user asks to make a model the default, set `active_profile` to that model's `model_id`. If they only ask to add the model, preserve the existing `active_profile`.
|
|
132
|
+
- For API keys, never read secret files and never print or echo the real key. Prefer environment variable references such as `api_key = "${DEEPSEEK_API_KEY}"`. If the user explicitly provides a raw key and asks to save it, write it only to the local ignored config and do not repeat it in the final answer.
|
|
133
|
+
- For hook requests, write `[[hooks]]` entries with `event`, optional `matcher`, `command`, optional `timeout`, optional `status_message`, optional `if`, and optional `once`.
|
|
134
|
+
- Use snake_case event names in TOML, such as `pre_tool_use`, `post_tool_use`, `user_prompt_submit`, `session_start`, `stop`, and `permission_request`.
|
|
135
|
+
- For a task-completion popup hook, use `event = "stop"` and choose a platform command. On macOS, use an `osascript` notification command. On Windows, use a PowerShell command such as `Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show(...)`. Do not use a macOS command on Windows or a Windows command on macOS/Linux.
|
|
136
|
+
- After editing model or hook config, summarize the config path, which profile or hook changed, and any missing user action such as setting an environment variable. Do not include secrets in the summary.
|
|
137
|
+
|
|
138
|
+
#### 5.5. Web Access
|
|
123
139
|
|
|
124
140
|
- Do not pass URLs to filesystem tools. `http(s)://...` and `//host/path` are web or internal-document targets, not local paths.
|
|
125
141
|
- Use `web_search` for current or external public information that is not available in local files, such as weather, news, prices, release status, schedules, laws, regulations, and current product docs.
|
|
126
142
|
- Use `web_fetch` only for public http(s) URLs supplied by the user or found through `web_search`; it returns bounded readable text and must not be used for localhost, private network URLs, or binary files.
|
|
127
|
-
- For authenticated
|
|
128
|
-
- If the Remi Skills index lists a relevant internal-platform skill
|
|
143
|
+
- For authenticated private URLs, public web fetch may only prove that the page is login-gated.
|
|
144
|
+
- If the Remi Skills index lists a relevant internal-platform skill, load that skill before concluding the content cannot be read.
|
|
129
145
|
- If a real-time question is missing a necessary scope, ask one concise clarifying question before searching. For weather, ask for the city or region if it is not provided.
|
|
130
146
|
- For relative-date queries such as today, tomorrow, or yesterday, use the current local date from the runtime context. Do not add an old year from model memory. If the date is not needed in the query, omit the year.
|
|
131
147
|
|