@wangzhizhi/remi 0.0.1-alpha → 0.0.1-alpha.1
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/dist/doctor.js +13 -14
- package/dist/version.js +1 -1
- package/node_modules/@remi/compact/package.json +1 -1
- package/node_modules/@remi/config/dist/index.js +55 -23
- package/node_modules/@remi/config/package.json +1 -1
- package/node_modules/@remi/core/dist/contextBuilder.js +3 -3
- package/node_modules/@remi/core/dist/index.js +3 -2
- package/node_modules/@remi/core/dist/promptFiles.js +17 -0
- package/node_modules/@remi/core/dist/responseStyles.js +2 -2
- package/node_modules/@remi/core/package.json +1 -1
- package/node_modules/@remi/llm/package.json +1 -1
- package/node_modules/@remi/memory/dist/index.js +10 -3
- 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 +23 -13
- package/node_modules/@remi/sessions/package.json +1 -1
- package/node_modules/@remi/skills/package.json +1 -1
- package/node_modules/@remi/terminal-markdown/package.json +1 -1
- package/node_modules/@remi/tools/dist/index.js +5 -2
- package/node_modules/@remi/tools/package.json +1 -1
- package/package.json +12 -11
- package/prompt/base-system.md +34 -0
- package/prompt/tool-use-system.md +75 -0
package/dist/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { loadRemiConfig } from '@remi/config';
|
|
4
|
+
import { loadRemiConfig, projectStateDir } from '@remi/config';
|
|
5
5
|
import { createModelRouter } from '@remi/llm';
|
|
6
6
|
function commandVersion(command, args = ['--version']) {
|
|
7
7
|
return execFileSync(command, args, {
|
|
@@ -38,36 +38,35 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
38
38
|
detail: cwd,
|
|
39
39
|
});
|
|
40
40
|
try {
|
|
41
|
-
const
|
|
42
|
-
mkdirSync(
|
|
43
|
-
const probePath = join(
|
|
41
|
+
const stateDir = projectStateDir(cwd);
|
|
42
|
+
mkdirSync(stateDir, { recursive: true });
|
|
43
|
+
const probePath = join(stateDir, '.doctor-write-test');
|
|
44
44
|
writeFileSync(probePath, 'ok');
|
|
45
45
|
rmSync(probePath, { force: true });
|
|
46
46
|
checks.push({
|
|
47
|
-
name: '
|
|
47
|
+
name: 'stateDir',
|
|
48
48
|
ok: true,
|
|
49
|
-
detail:
|
|
49
|
+
detail: stateDir,
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
catch (error) {
|
|
53
53
|
checks.push({
|
|
54
|
-
name: '
|
|
54
|
+
name: 'stateDir',
|
|
55
55
|
ok: false,
|
|
56
56
|
detail: error instanceof Error ? error.message : String(error),
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
const keyNames = ['REMI_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'DEEPSEEK_API_KEY'];
|
|
60
|
-
const configuredKeys = keyNames.filter(name => Boolean(process.env[name]));
|
|
61
|
-
checks.push({
|
|
62
|
-
name: 'apiKey',
|
|
63
|
-
ok: configuredKeys.length > 0,
|
|
64
|
-
detail: configuredKeys.length > 0 ? configuredKeys.join(', ') : 'not configured',
|
|
65
|
-
});
|
|
66
59
|
try {
|
|
67
60
|
const loaded = loadRemiConfig({ cwd });
|
|
68
61
|
const router = createModelRouter(loaded.config);
|
|
69
62
|
const providers = router.providers.list();
|
|
63
|
+
const configuredProviders = providers.filter(provider => provider.apiKeyStatus === 'configured');
|
|
70
64
|
const missingProviders = providers.filter(provider => provider.apiKeyStatus === 'missing');
|
|
65
|
+
checks.push({
|
|
66
|
+
name: 'apiKey',
|
|
67
|
+
ok: configuredProviders.length > 0,
|
|
68
|
+
detail: configuredProviders.length > 0 ? configuredProviders.map(provider => provider.alias).join(', ') : 'not configured',
|
|
69
|
+
});
|
|
71
70
|
checks.push({
|
|
72
71
|
name: 'profile',
|
|
73
72
|
ok: Boolean(loaded.config.activeProfile),
|
package/dist/version.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
4
5
|
export const configPackageName = '@remi/config';
|
|
5
6
|
export const languageCodes = ['en', 'zh-Hans'];
|
|
6
7
|
export const modelRoles = ['main', 'planning', 'subagent', 'compact', 'memory', 'toolRepair', 'embedding'];
|
|
@@ -97,15 +98,13 @@ export function createDeepSeekPresetConfig() {
|
|
|
97
98
|
export function createClaudeCodeEnvConfig(env = process.env) {
|
|
98
99
|
const mainModel = env['ANTHROPIC_MODEL'];
|
|
99
100
|
const smallModel = env['ANTHROPIC_SMALL_FAST_MODEL'] ?? mainModel;
|
|
100
|
-
const apiKeyEnv = env['ANTHROPIC_AUTH_TOKEN'] ? 'ANTHROPIC_AUTH_TOKEN' : env['ANTHROPIC_API_KEY'] ? 'ANTHROPIC_API_KEY' : undefined;
|
|
101
101
|
const baseURL = env['ANTHROPIC_BASE_URL'] ?? defaultAnthropicBaseURL;
|
|
102
102
|
const config = {};
|
|
103
|
-
if (
|
|
103
|
+
if (env['ANTHROPIC_BASE_URL']) {
|
|
104
104
|
config.providers = {
|
|
105
105
|
claudeCode: {
|
|
106
106
|
type: 'anthropic-compatible',
|
|
107
107
|
baseURL,
|
|
108
|
-
...(apiKeyEnv ? { apiKey: `\${${apiKeyEnv}}`, apiKeyEnv } : {}),
|
|
109
108
|
},
|
|
110
109
|
};
|
|
111
110
|
}
|
|
@@ -148,41 +147,49 @@ export function createClaudeCodeEnvConfig(env = process.env) {
|
|
|
148
147
|
}
|
|
149
148
|
export function configPaths(options = {}) {
|
|
150
149
|
const cwd = options.cwd ?? process.cwd();
|
|
151
|
-
const homeDir = options.homeDir ?? homedir();
|
|
150
|
+
const homeDir = options.homeDir ?? process.env['REMI_HOME'] ?? homedir();
|
|
152
151
|
const sources = [
|
|
153
152
|
{ kind: 'user', path: userConfigPath(homeDir), exists: false },
|
|
154
|
-
{ kind: 'project', path:
|
|
155
|
-
{ kind: 'local', path: join(cwd, '.agent', 'config.local.json'), exists: false },
|
|
153
|
+
{ kind: 'project', path: projectConfigPath(cwd, homeDir), exists: false },
|
|
156
154
|
];
|
|
157
155
|
return sources.map(source => ({ ...source, exists: existsSync(source.path) }));
|
|
158
156
|
}
|
|
159
|
-
export function userConfigPath(homeDir = homedir()) {
|
|
157
|
+
export function userConfigPath(homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
160
158
|
return join(homeDir, '.remi', 'config.json');
|
|
161
159
|
}
|
|
162
|
-
export function
|
|
160
|
+
export function remiHomeDir(homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
161
|
+
return join(homeDir, '.remi');
|
|
162
|
+
}
|
|
163
|
+
export function projectStateKey(cwd = process.cwd()) {
|
|
164
|
+
return createHash('sha256').update(resolve(cwd)).digest('hex').slice(0, 16);
|
|
165
|
+
}
|
|
166
|
+
export function projectStateDir(cwd = process.cwd(), homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
167
|
+
return join(remiHomeDir(homeDir), 'projects', projectStateKey(cwd));
|
|
168
|
+
}
|
|
169
|
+
export function readUserRemiConfig(homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
163
170
|
const path = userConfigPath(homeDir);
|
|
164
171
|
if (!existsSync(path)) {
|
|
165
172
|
return {};
|
|
166
173
|
}
|
|
167
174
|
return parseConfigFile(path);
|
|
168
175
|
}
|
|
169
|
-
export function writeUserRemiConfig(config, homeDir = homedir()) {
|
|
170
|
-
mkdirSync(
|
|
176
|
+
export function writeUserRemiConfig(config, homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
177
|
+
mkdirSync(remiHomeDir(homeDir), { recursive: true });
|
|
171
178
|
writeFileSync(userConfigPath(homeDir), `${JSON.stringify(config, null, 2)}\n`);
|
|
172
179
|
}
|
|
173
|
-
export function projectConfigPath(cwd = process.cwd()) {
|
|
174
|
-
return join(cwd,
|
|
180
|
+
export function projectConfigPath(cwd = process.cwd(), homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
181
|
+
return join(projectStateDir(cwd, homeDir), 'config.json');
|
|
175
182
|
}
|
|
176
|
-
export function readProjectRemiConfig(cwd = process.cwd()) {
|
|
177
|
-
const path = projectConfigPath(cwd);
|
|
183
|
+
export function readProjectRemiConfig(cwd = process.cwd(), homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
184
|
+
const path = projectConfigPath(cwd, homeDir);
|
|
178
185
|
if (!existsSync(path)) {
|
|
179
186
|
return {};
|
|
180
187
|
}
|
|
181
188
|
return parseConfigFile(path);
|
|
182
189
|
}
|
|
183
|
-
export function writeProjectRemiConfig(config, cwd = process.cwd()) {
|
|
184
|
-
mkdirSync(
|
|
185
|
-
writeFileSync(projectConfigPath(cwd), `${JSON.stringify(config, null, 2)}\n`);
|
|
190
|
+
export function writeProjectRemiConfig(config, cwd = process.cwd(), homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
191
|
+
mkdirSync(projectStateDir(cwd, homeDir), { recursive: true });
|
|
192
|
+
writeFileSync(projectConfigPath(cwd, homeDir), `${JSON.stringify(config, null, 2)}\n`);
|
|
186
193
|
}
|
|
187
194
|
export function isPermissionProfile(value) {
|
|
188
195
|
return typeof value === 'string' && permissionProfiles.includes(value);
|
|
@@ -285,10 +292,7 @@ export function mergeRemiConfig(...configs) {
|
|
|
285
292
|
...(mergedDisabledSkills ? { disabled: mergedDisabledSkills } : {}),
|
|
286
293
|
}
|
|
287
294
|
: undefined;
|
|
288
|
-
const providers =
|
|
289
|
-
...merged.providers,
|
|
290
|
-
...config.providers,
|
|
291
|
-
};
|
|
295
|
+
const providers = mergeProviderConfigs(merged.providers, config.providers);
|
|
292
296
|
const models = {
|
|
293
297
|
...merged.models,
|
|
294
298
|
...config.models,
|
|
@@ -350,6 +354,33 @@ function mergeProfiles(base, next) {
|
|
|
350
354
|
}
|
|
351
355
|
return result;
|
|
352
356
|
}
|
|
357
|
+
function mergeProviderConfigs(base, next) {
|
|
358
|
+
const result = {};
|
|
359
|
+
for (const [alias, provider] of Object.entries(base ?? {})) {
|
|
360
|
+
result[alias] = { ...provider };
|
|
361
|
+
}
|
|
362
|
+
for (const [alias, provider] of Object.entries(next ?? {})) {
|
|
363
|
+
result[alias] = {
|
|
364
|
+
...(result[alias] ?? {}),
|
|
365
|
+
...provider,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
function stripProviderSecrets(config) {
|
|
371
|
+
if (!config.providers) {
|
|
372
|
+
return config;
|
|
373
|
+
}
|
|
374
|
+
const providers = {};
|
|
375
|
+
for (const [alias, provider] of Object.entries(config.providers)) {
|
|
376
|
+
const { apiKey: _apiKey, apiKeyEnv: _apiKeyEnv, ...publicProvider } = provider;
|
|
377
|
+
providers[alias] = publicProvider;
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
...config,
|
|
381
|
+
providers,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
353
384
|
export function loadRemiConfig(options = {}) {
|
|
354
385
|
const sources = configPaths(options);
|
|
355
386
|
const diagnostics = [];
|
|
@@ -362,7 +393,8 @@ export function loadRemiConfig(options = {}) {
|
|
|
362
393
|
continue;
|
|
363
394
|
}
|
|
364
395
|
try {
|
|
365
|
-
|
|
396
|
+
const parsed = parseConfigFile(source.path);
|
|
397
|
+
configs.push(source.kind === 'project' ? stripProviderSecrets(parsed) : parsed);
|
|
366
398
|
}
|
|
367
399
|
catch (error) {
|
|
368
400
|
diagnostics.push({
|
|
@@ -81,7 +81,7 @@ export function buildProviderContext(options) {
|
|
|
81
81
|
if (memoryPrompt) {
|
|
82
82
|
addMessageLayer({
|
|
83
83
|
id: 'memory',
|
|
84
|
-
source: '
|
|
84
|
+
source: '~/.remi memory recall',
|
|
85
85
|
messages,
|
|
86
86
|
layers,
|
|
87
87
|
layerMessages: [{ role: 'system', content: memoryPrompt }],
|
|
@@ -94,7 +94,7 @@ export function buildProviderContext(options) {
|
|
|
94
94
|
else {
|
|
95
95
|
addTrace(layers, {
|
|
96
96
|
id: 'memory',
|
|
97
|
-
source: '
|
|
97
|
+
source: '~/.remi memory recall',
|
|
98
98
|
included: false,
|
|
99
99
|
messageCount: 0,
|
|
100
100
|
charCount: 0,
|
|
@@ -159,7 +159,7 @@ export function buildProviderContext(options) {
|
|
|
159
159
|
}
|
|
160
160
|
addMessageLayer({
|
|
161
161
|
id: 'recent',
|
|
162
|
-
source: '
|
|
162
|
+
source: '~/.remi sessions recent user/assistant transcript',
|
|
163
163
|
messages,
|
|
164
164
|
layers,
|
|
165
165
|
layerMessages: applyMessagesBudget(options.recentMessages ?? [], options.budgets?.recent, options.tokenBudgets?.recent, tokenEstimator, tokenEstimateProfile),
|
|
@@ -3,14 +3,15 @@ import { buildBaseSystemPrompt, defaultResponseStyleId, resolveResponseStyle, }
|
|
|
3
3
|
import { buildProviderContext } from './contextBuilder.js';
|
|
4
4
|
import { buildDirectoryOverviewPrelude } from './directoryOverview.js';
|
|
5
5
|
import { buildProjectInstructionsContext } from './projectInstructions.js';
|
|
6
|
+
import { readPromptFile } from './promptFiles.js';
|
|
6
7
|
import { emptyMemoryRecall, recallRelevantMemories, recordMemoryUse } from '@remi/memory';
|
|
7
8
|
import { compactSession, estimateSessionTokens, estimateTextTokens, eventsAfterLatestCompact, latestCompactSummary, maybeUpdateSessionMemory, readSessionMemory } from '@remi/compact';
|
|
8
9
|
import { formatSkillIndexContext, loadSkillContent, loadSkillIndex } from '@remi/skills';
|
|
9
10
|
export { buildBaseSystemPrompt, buildResponseLanguageSystemPrompt, buildResponseStyleSystemPrompt, defaultResponseStyleId, isResponseStyleId, resolveResponseStyle, responseStylePresets, } from './responseStyles.js';
|
|
10
11
|
export { buildProviderContext, } from './contextBuilder.js';
|
|
11
12
|
export { buildProjectInstructionsContext } from './projectInstructions.js';
|
|
13
|
+
export { promptFileCandidates, readPromptFile } from './promptFiles.js';
|
|
12
14
|
import { createModelRouter, createProviderClient, createTokenEstimatorRegistry, } from '@remi/llm';
|
|
13
|
-
import { readFileSync } from 'node:fs';
|
|
14
15
|
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
15
16
|
import { createSessionStore, readSessionEvents, writeToolResultArtifact } from '@remi/sessions';
|
|
16
17
|
import { createReadOnlyDryRunExecutor as createRemiReadOnlyDryRunExecutor, createLocalToolExecutor as createRemiLocalToolExecutor, createBuiltInToolRegistry as createRemiBuiltInToolRegistry, createToolRegistry as createRemiToolRegistry, createReadOnlyFileSystemExecutor as createRemiReadOnlyFileSystemExecutor, readFileTool, listFilesTool, searchTextTool, globTool, todoWriteTool, runCommandTool, } from '@remi/tools';
|
|
@@ -2105,7 +2106,7 @@ function readToolUseSystemPromptTemplate() {
|
|
|
2105
2106
|
return cachedToolUseSystemPromptTemplate;
|
|
2106
2107
|
}
|
|
2107
2108
|
try {
|
|
2108
|
-
cachedToolUseSystemPromptTemplate =
|
|
2109
|
+
cachedToolUseSystemPromptTemplate = readPromptFile('tool-use-system.md');
|
|
2109
2110
|
}
|
|
2110
2111
|
catch {
|
|
2111
2112
|
cachedToolUseSystemPromptTemplate = [
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
export function promptFileCandidates(fileName, importerUrl = import.meta.url) {
|
|
4
|
+
return [
|
|
5
|
+
new URL(`../../../../prompt/${fileName}`, importerUrl),
|
|
6
|
+
new URL(`../../../prompt/${fileName}`, importerUrl),
|
|
7
|
+
];
|
|
8
|
+
}
|
|
9
|
+
export function readPromptFile(fileName, importerUrl = import.meta.url) {
|
|
10
|
+
const candidates = promptFileCandidates(fileName, importerUrl);
|
|
11
|
+
for (const candidate of candidates) {
|
|
12
|
+
if (existsSync(candidate)) {
|
|
13
|
+
return readFileSync(candidate, 'utf8').trim();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Missing prompt file ${fileName}. Searched: ${candidates.map(candidate => fileURLToPath(candidate)).join(', ')}`);
|
|
17
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readPromptFile } from './promptFiles.js';
|
|
2
2
|
export const defaultResponseStyleId = 'remi';
|
|
3
3
|
export const responseStylePresets = [
|
|
4
4
|
{
|
|
@@ -93,6 +93,6 @@ export function buildResponseStyleSystemPrompt(styleId) {
|
|
|
93
93
|
return instructions.map(instruction => `- ${instruction}`).join('\n');
|
|
94
94
|
}
|
|
95
95
|
function readBaseSystemPromptTemplate() {
|
|
96
|
-
cachedBaseSystemPromptTemplate ??=
|
|
96
|
+
cachedBaseSystemPromptTemplate ??= readPromptFile('base-system.md');
|
|
97
97
|
return cachedBaseSystemPromptTemplate;
|
|
98
98
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
5
|
export const memoryPackageName = '@remi/memory';
|
|
5
6
|
export const memoryTypes = ['user', 'project', 'feedback', 'reference'];
|
|
6
7
|
const defaultRecallLimit = 5;
|
|
7
8
|
const defaultMaxBodyChars = 6_000;
|
|
8
9
|
export function memoryDir(cwd = process.cwd()) {
|
|
9
|
-
return join(
|
|
10
|
+
return join(remiDataDir(), 'memory', 'projects', projectMemoryKey(cwd));
|
|
11
|
+
}
|
|
12
|
+
export function remiDataDir(homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
13
|
+
return join(homeDir, '.remi');
|
|
14
|
+
}
|
|
15
|
+
export function projectMemoryKey(cwd = process.cwd()) {
|
|
16
|
+
return createHash('sha256').update(resolve(cwd)).digest('hex').slice(0, 16);
|
|
10
17
|
}
|
|
11
18
|
export function memoriesDir(cwd = process.cwd()) {
|
|
12
19
|
return join(memoryDir(cwd), 'memories');
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
5
|
export const sessionsPackageName = '@remi/sessions';
|
|
5
6
|
export function createSessionId() {
|
|
6
7
|
return randomUUID();
|
|
7
8
|
}
|
|
8
9
|
export function sessionsDir(cwd = process.cwd()) {
|
|
9
|
-
return join(cwd, '
|
|
10
|
+
return join(projectStateDir(cwd), 'sessions');
|
|
10
11
|
}
|
|
11
12
|
export function artifactsDir(cwd = process.cwd()) {
|
|
12
|
-
return join(cwd, '
|
|
13
|
+
return join(projectStateDir(cwd), 'artifacts');
|
|
13
14
|
}
|
|
14
15
|
export function toolResultArtifactsDir(cwd, sessionId) {
|
|
15
16
|
return join(artifactsDir(cwd), 'tool-results', safeArtifactSegment(sessionId));
|
|
@@ -24,13 +25,13 @@ export function sessionPermissionRulesPath(cwd, sessionId) {
|
|
|
24
25
|
return join(sessionsDir(cwd), `${sessionId}.permissions.json`);
|
|
25
26
|
}
|
|
26
27
|
export function sessionIndexPath(cwd = process.cwd()) {
|
|
27
|
-
return join(cwd, '
|
|
28
|
+
return join(sessionsDir(cwd), 'index.json');
|
|
28
29
|
}
|
|
29
30
|
export function activeSessionPath(cwd = process.cwd()) {
|
|
30
|
-
return join(cwd, '
|
|
31
|
+
return join(projectStateDir(cwd), 'session.json');
|
|
31
32
|
}
|
|
32
33
|
export function projectInputHistoryPath(cwd = process.cwd()) {
|
|
33
|
-
return join(cwd, '
|
|
34
|
+
return join(projectStateDir(cwd), 'input-history.json');
|
|
34
35
|
}
|
|
35
36
|
export function sessionExists(cwd, sessionId) {
|
|
36
37
|
return existsSync(sessionPath(cwd, sessionId));
|
|
@@ -52,7 +53,7 @@ export function readActiveSessionId(cwd = process.cwd()) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
export function writeActiveSessionId(cwd, sessionId) {
|
|
55
|
-
|
|
56
|
+
ensureProjectStateDir(cwd);
|
|
56
57
|
writeFileSync(activeSessionPath(cwd), `${JSON.stringify({ sessionId, updatedAt: new Date().toISOString() }, null, 2)}\n`);
|
|
57
58
|
}
|
|
58
59
|
export function writeToolResultArtifact(cwd, sessionId, input) {
|
|
@@ -77,7 +78,7 @@ export function writeToolResultArtifact(cwd, sessionId, input) {
|
|
|
77
78
|
return {
|
|
78
79
|
id,
|
|
79
80
|
kind: 'tool-result',
|
|
80
|
-
path
|
|
81
|
+
path,
|
|
81
82
|
bytes,
|
|
82
83
|
contentType,
|
|
83
84
|
createdAt,
|
|
@@ -196,11 +197,20 @@ export function createSessionStore(cwd = process.cwd(), sessionId = createSessio
|
|
|
196
197
|
},
|
|
197
198
|
};
|
|
198
199
|
}
|
|
199
|
-
function
|
|
200
|
-
|
|
200
|
+
export function remiDataDir(homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
201
|
+
return join(homeDir, '.remi');
|
|
202
|
+
}
|
|
203
|
+
export function projectStateKey(cwd = process.cwd()) {
|
|
204
|
+
return createHash('sha256').update(resolve(cwd)).digest('hex').slice(0, 16);
|
|
205
|
+
}
|
|
206
|
+
export function projectStateDir(cwd = process.cwd(), homeDir = process.env['REMI_HOME'] ?? homedir()) {
|
|
207
|
+
return join(remiDataDir(homeDir), 'projects', projectStateKey(cwd));
|
|
208
|
+
}
|
|
209
|
+
function ensureProjectStateDir(cwd) {
|
|
210
|
+
mkdirSync(projectStateDir(cwd), { recursive: true });
|
|
201
211
|
}
|
|
202
212
|
function ensureSessionDirs(cwd) {
|
|
203
|
-
|
|
213
|
+
ensureProjectStateDir(cwd);
|
|
204
214
|
mkdirSync(sessionsDir(cwd), { recursive: true });
|
|
205
215
|
}
|
|
206
216
|
function safeArtifactSegment(value) {
|
|
@@ -229,7 +239,7 @@ function readProjectInputHistoryEntries(cwd) {
|
|
|
229
239
|
return [];
|
|
230
240
|
}
|
|
231
241
|
function writeProjectInputHistory(cwd, history) {
|
|
232
|
-
|
|
242
|
+
ensureProjectStateDir(cwd);
|
|
233
243
|
writeFileSync(projectInputHistoryPath(cwd), `${JSON.stringify(history, null, 2)}\n`);
|
|
234
244
|
}
|
|
235
245
|
function readSessionIndex(cwd) {
|
|
@@ -3628,7 +3628,7 @@ function isSensitivePath(absolutePath) {
|
|
|
3628
3628
|
const normalized = absolutePath.split(sep).join('/');
|
|
3629
3629
|
const name = basename(absolutePath);
|
|
3630
3630
|
const lowerName = name.toLowerCase();
|
|
3631
|
-
if (normalized.
|
|
3631
|
+
if (pathHasSegment(normalized, '.agent') || pathHasSegment(normalized, '.remi')) {
|
|
3632
3632
|
return true;
|
|
3633
3633
|
}
|
|
3634
3634
|
return (lowerName === '.env' ||
|
|
@@ -3647,6 +3647,9 @@ function isSensitivePath(absolutePath) {
|
|
|
3647
3647
|
lowerName.includes('token') ||
|
|
3648
3648
|
lowerName.includes('secret'));
|
|
3649
3649
|
}
|
|
3650
|
+
function pathHasSegment(normalizedPath, segment) {
|
|
3651
|
+
return normalizedPath.split('/').includes(segment);
|
|
3652
|
+
}
|
|
3650
3653
|
async function collectDirectoryEntries(root, context, options) {
|
|
3651
3654
|
const items = [];
|
|
3652
3655
|
let truncated = false;
|
|
@@ -3699,7 +3702,7 @@ async function collectFiles(root, context, options) {
|
|
|
3699
3702
|
};
|
|
3700
3703
|
}
|
|
3701
3704
|
function isSkippedDirectory(name) {
|
|
3702
|
-
return name === 'node_modules' || name === '.git' || name === 'dist' || name === '.agent';
|
|
3705
|
+
return name === 'node_modules' || name === '.git' || name === 'dist' || name === '.agent' || name === '.remi';
|
|
3703
3706
|
}
|
|
3704
3707
|
function sliceLineRange(content, startLine, endLine) {
|
|
3705
3708
|
if (startLine === undefined && endLine === undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wangzhizhi/remi",
|
|
3
|
-
"version": "0.0.1-alpha",
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
4
|
"description": "Remi AI coding agent CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
"node": ">=24 <25"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@remi/compact": "0.0.1-alpha",
|
|
14
|
-
"@remi/config": "0.0.1-alpha",
|
|
15
|
-
"@remi/core": "0.0.1-alpha",
|
|
16
|
-
"@remi/llm": "0.0.1-alpha",
|
|
17
|
-
"@remi/memory": "0.0.1-alpha",
|
|
18
|
-
"@remi/permissions": "0.0.1-alpha",
|
|
19
|
-
"@remi/sessions": "0.0.1-alpha",
|
|
20
|
-
"@remi/skills": "0.0.1-alpha",
|
|
21
|
-
"@remi/terminal-markdown": "0.0.1-alpha",
|
|
22
|
-
"@remi/tools": "0.0.1-alpha",
|
|
13
|
+
"@remi/compact": "0.0.1-alpha.1",
|
|
14
|
+
"@remi/config": "0.0.1-alpha.1",
|
|
15
|
+
"@remi/core": "0.0.1-alpha.1",
|
|
16
|
+
"@remi/llm": "0.0.1-alpha.1",
|
|
17
|
+
"@remi/memory": "0.0.1-alpha.1",
|
|
18
|
+
"@remi/permissions": "0.0.1-alpha.1",
|
|
19
|
+
"@remi/sessions": "0.0.1-alpha.1",
|
|
20
|
+
"@remi/skills": "0.0.1-alpha.1",
|
|
21
|
+
"@remi/terminal-markdown": "0.0.1-alpha.1",
|
|
22
|
+
"@remi/tools": "0.0.1-alpha.1",
|
|
23
23
|
"highlight.js": "11.11.1",
|
|
24
24
|
"ink": "7.0.4",
|
|
25
25
|
"marked": "17.0.1",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
42
|
+
"prompt",
|
|
42
43
|
"node_modules/@remi",
|
|
43
44
|
"README.md"
|
|
44
45
|
],
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Remi Base System Prompt
|
|
2
|
+
|
|
3
|
+
You are Remi, a local AI coding agent.
|
|
4
|
+
|
|
5
|
+
## Operating Loop
|
|
6
|
+
|
|
7
|
+
- Start from the user's concrete goal and the local project context.
|
|
8
|
+
- Inspect before changing code when the answer depends on files, configuration, or existing behavior.
|
|
9
|
+
- For multi-step or risky work, create a visible plan with `todo_write` before executing, keep exactly one active item in progress, and update the plan as steps complete. When the runtime says plan-first is required for an implementation request, record the plan first and then continue when normal tools become available; ask for confirmation only when the user requested a plan-only answer, the scope is ambiguous, or the next action is destructive.
|
|
10
|
+
- Keep changes narrow. Every changed line should connect to the user's request or a defect found while doing it.
|
|
11
|
+
- Prefer the smallest durable design that fits the current codebase. Do not add speculative abstraction, compatibility, or feature surface.
|
|
12
|
+
- Verify with the most relevant command or test you can run. If you cannot verify, say exactly why.
|
|
13
|
+
|
|
14
|
+
## Context Discipline
|
|
15
|
+
|
|
16
|
+
- Treat project instructions, plan files, transcripts, memory, and compact summaries as different context layers.
|
|
17
|
+
- Do not rely on hidden memory for durable project state. When the project has an explicit plan or agent guide, keep it current.
|
|
18
|
+
- Save only stable, non-sensitive facts to memory. Do not save secrets, temporary UI state, or facts that can be read from the repo.
|
|
19
|
+
- A compact summary must preserve the active goal, relevant files, decisions, failing checks, unresolved questions, and next step.
|
|
20
|
+
|
|
21
|
+
## Tool And Safety Discipline
|
|
22
|
+
|
|
23
|
+
- Use tools when they can answer a local or technical question more reliably than guessing.
|
|
24
|
+
- Prefer structured, read-only inspection before write or shell actions.
|
|
25
|
+
- Never expose API keys, tokens, cookies, private keys, or local credentials.
|
|
26
|
+
- Destructive or irreversible operations require an explicit user request and the active permission policy to allow them.
|
|
27
|
+
|
|
28
|
+
## Communication
|
|
29
|
+
|
|
30
|
+
- Match the user's language for user-facing prose.
|
|
31
|
+
- If the user writes in Chinese, write assistant prose, visible plan items, brief transition text, blockers, and final answers in Simplified Chinese. Do not use English filler or mixed-language lead-ins such as "Let me", "First", "I will", or "I need to"; write those clauses in Chinese instead.
|
|
32
|
+
- Be direct, factual, and task-focused.
|
|
33
|
+
- Surface uncertainty, tradeoffs, and blockers early.
|
|
34
|
+
- For simple greetings or acknowledgements, answer briefly and wait for the actual task.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
You can use Remi's local tools when the user asks about local files, directories, code, git state, or read-only command output.
|
|
2
|
+
|
|
3
|
+
Current working directory: {{cwd}}
|
|
4
|
+
{{home_line}}
|
|
5
|
+
Host platform: {{host_platform}}
|
|
6
|
+
|
|
7
|
+
Tool choice:
|
|
8
|
+
- Use `todo_write` to create or update the visible plan checklist for multi-step work. Send the full current list each time, with at most one `in_progress` item.
|
|
9
|
+
- A plan is task state, not execution evidence. Do not claim files changed, commands passed, or validation succeeded just because a plan item is completed.
|
|
10
|
+
- Do not mark a plan item about toolchain availability, compilation, tests, or validation as completed until the current turn has the corresponding successful command result.
|
|
11
|
+
- If the runtime says plan-first is required, use only read-only inspection and `todo_write` until the plan is recorded. For implementation requests, continue once normal tools become available; ask for approval only when the user requested a plan-only answer, the scope is ambiguous, or the next action is destructive.
|
|
12
|
+
- Use `list_files` to inspect an unfamiliar directory.
|
|
13
|
+
- Use `glob` to find files by file name, extension, suffix, or path pattern such as `*.go`, `go.*`, `**/*.ts`, or `**/main.go`.
|
|
14
|
+
- Use `search_text` to search inside file contents only. Do not use it for file names, glob wildcards, extensions, or path patterns.
|
|
15
|
+
- Use `read_file` to read a known file. If you do not know the exact path, discover it with `list_files` or `glob` first instead of guessing likely sibling paths.
|
|
16
|
+
- Use `create_directory` to create a requested directory.
|
|
17
|
+
- Use `edit_file` for targeted changes to existing UTF-8 files. Read the full file with `read_file` first, then edit with an exact `oldString` and `newString`.
|
|
18
|
+
- Use `write_file` for new UTF-8 source/text files or an intentional full-file replacement. Do not overwrite existing files unless the user asked for that or the target is clearly meant to be replaced.
|
|
19
|
+
- Use `delete_file` when the user explicitly asks to delete or remove one file; for multiple files, call `delete_file` once per file. Prefer this over shell `rm` because it is easier to review.
|
|
20
|
+
- Use `delete_directory` when the user explicitly asks to delete or remove one empty directory; for multiple empty directories, call `delete_directory` once per directory. Prefer this over shell `rmdir` because it is easier to review.
|
|
21
|
+
- For create/update/edit/write/delete requests, do not say the file or directory was changed or deleted unless a current-turn successful `edit_file`, `write_file`, `create_directory`, `delete_file`, or `delete_directory` result for that change exists. Reading a file is not a modification.
|
|
22
|
+
- 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.
|
|
23
|
+
- 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.
|
|
24
|
+
- Prior assistant messages are conversation history, not proof of the current filesystem state. For current file status, code content, edits, or verification, use current-turn tool results as the source of truth.
|
|
25
|
+
- Use `run_command` only for simple read-only local inspection commands when structured tools cannot express the task well.
|
|
26
|
+
- Use `execute_shell` for build, test, and validation commands after the relevant file work is ready, such as `go test ./...`, `go vet ./...`, `pnpm test`, or `pnpm run build`. Set its `cwd` argument when the command must run from a package/project directory. If you must express the working directory inside the command, use a single-line `cd <dir> && command` form. The runtime will request approval when needed.
|
|
27
|
+
- For simple project scaffolding, create the directory and minimal source/config files directly when the file contents are straightforward. If an init or run command is denied, do not stop at an apology; write the equivalent minimal files when safe, then tell the user which command to run manually.
|
|
28
|
+
- When the user confirms an overwrite or simple scaffold change, finish the file write first. Run validation through `execute_shell` only when the command is relevant to the completed work.
|
|
29
|
+
|
|
30
|
+
Path resolution policy:
|
|
31
|
+
- The current working directory above is Remi's runtime cwd. It is not automatically the user's target project.
|
|
32
|
+
- When the context includes "Recent Filesystem Locations", use those path facts to resolve project names mentioned again in later turns.
|
|
33
|
+
- If a previous tool result established `/Users/.../Desktop/math-test`, then a later request about `math-test` should target that absolute directory, not `{{cwd}}/math-test`.
|
|
34
|
+
- Before creating a new top-level directory whose name appeared earlier in the session, prefer the known absolute project root or inspect it. Only create a same-named directory under the runtime cwd when the user explicitly asks for this Remi repository.
|
|
35
|
+
- For build/test/validation commands in a remembered project, pass `execute_shell.cwd` as the known absolute project root.
|
|
36
|
+
- For existing projects with nested directories, inspect the directory tree before reading conventional filenames such as `sort.h`, `main.go`, or `package.json`; those files may live under subdirectories.
|
|
37
|
+
|
|
38
|
+
Directory overview policy:
|
|
39
|
+
- For broad questions about what a directory contains, do not stop after the top-level list.
|
|
40
|
+
- Inspect one useful next layer, such as key guide files, important subdirectories, markdown files, summaries, indexes, or recent content files.
|
|
41
|
+
- Prefer a small number of focused follow-up tool calls over recursive dumping.
|
|
42
|
+
- When the available context includes a suggested tree view, use it as the factual structure for your answer.
|
|
43
|
+
- Prefer a compact directory tree first, then a short explanation of core files and likely project purpose.
|
|
44
|
+
- Explain the likely purpose of important files and folders, and say when an interpretation is inferred from names.
|
|
45
|
+
|
|
46
|
+
Search policy:
|
|
47
|
+
- Prefer native structured search tools over shell search commands.
|
|
48
|
+
- Use `search_text` only for text that should appear inside file contents. Use `glob` or `list_files` for file discovery.
|
|
49
|
+
- Do not call `search_text` with wildcard-only or filename-like queries such as `*`, `*.go`, `go.*`, `main.go`, or `src/**`.
|
|
50
|
+
- Keep searches narrow and summarize results by path, count, and meaning.
|
|
51
|
+
- If a result set is too large, ask for a narrower target or inspect a representative subset.
|
|
52
|
+
|
|
53
|
+
Command policy:
|
|
54
|
+
- `run_command` is not a general shell.
|
|
55
|
+
- Treat common host tasks as capabilities, not as one fixed Unix command. Use the platform candidate order for `network.ip`, `network.mac`, `network.ports`, `download.url`, `logs.search`, and `tool.install`.
|
|
56
|
+
- Prefer the current host platform's built-in query command first. For local IP: macOS/Linux can use `ifconfig -a`, Linux can use `ip addr show`, Windows can use `ipconfig /all`. For MAC address: macOS/Linux can use `ifconfig -a` or `ip link`, Windows can use `getmac /v /fo csv` or `ipconfig /all`. For listening ports: macOS/Linux can use `lsof -i :<port> -P -n`, `netstat -an`, or `ss -ltn`; Windows can use `netstat -ano`.
|
|
57
|
+
- If a read-only command returns exit code 127 or stderr says `Command not found`, treat it as a recoverable probe. Try a platform-equivalent allowlisted command before concluding failure.
|
|
58
|
+
- If the user wants a missing utility installed, use `execute_shell` with a narrow package-manager command so the runtime can request approval. Do not silently install tools. Do not add `sudo`; if a package manager needs admin rights, surface that as a separate approval/risk path.
|
|
59
|
+
- Allowed `run_command` executables include safe read-only forms of: `pwd`, `ls`, `cat`, `head`, `tail`, `wc`, `file`, `stat`, `du`, `df`, `grep`, `find`, `rg`, `ps`, `uname`, `date`, `whoami`, `id`, `which`, `where`, `ifconfig`, `ip`, `ipconfig`, `getmac`, `netstat`, `ss`, `lsof`, `arp`, and read-only `git` subcommands such as `status`, `diff`, `log`, `show`, `branch`, `rev-parse`, `ls-files`, `grep`, `blame`, `describe`, and `tag`.
|
|
60
|
+
- It rejects shell syntax, redirects, pipes, backgrounding, command substitution, multiline commands, unallowlisted commands, dangerous flags such as `find -exec` / `find -delete` or recursive unsafe `grep`, and sensitive file operands.
|
|
61
|
+
- Do not call `run_command` for `cd`, `go`, `go run`, `go test`, `go mod`, `npm`, `pnpm`, `node`, `python`, `bash`, `sh`, package managers, interpreters, build/test commands, write commands such as `cp`, `mv`, `sed -i`, `curl` / `wget` downloads, or chained commands such as `cd dir && command`.
|
|
62
|
+
- Never try to work around those restrictions.
|
|
63
|
+
- `execute_shell` is the approval path for validation/build commands, package manager installs, network downloads, and local commands with possible side effects, including `go mod`, `git init`, `g++`, `gcc`, `clang`, `sed`, `cp`, `mv`, `rm`, `rmdir`, `sh`, `curl`, `wget`, `brew`, `apt`, `apt-get`, `winget`, `choco`, `scoop`, and project-local scripts. It supports simple argv commands, a validated `cwd`, a safe `cd <dir>` segment, and `&&` / `;` sequencing, executed without a general shell.
|
|
64
|
+
- Do not call `execute_shell` with pipes, redirects, backgrounding, command substitution, environment variable assignments, or multiline commands. Use shell wrappers such as `bash` / `sh` only when the user explicitly asks for them or a project script requires them; they will require approval and should not get broad persisted allow rules.
|
|
65
|
+
- If an execution command is known to be outside `run_command` but fits `execute_shell`, use `execute_shell` instead of telling the user to run it manually.
|
|
66
|
+
- If `execute_shell` is denied but the task can still be completed by writing files, write the files first and then tell the user which command they can run manually.
|
|
67
|
+
- If a `run_command` result says `PERMISSION_DENIED`, `PERMISSION_REQUIRED`, or `COMMAND_DENIED`, do not retry the same command in a different shell form. If the command is a build/test/validation command that can be expressed as a simple `execute_shell` command, call `execute_shell` with `cwd` set to the target project directory so the runtime can request approval. Otherwise continue with the best available file/tool work and summarize the manual verification step.
|
|
68
|
+
- If an `execute_shell` result says `PERMISSION_DENIED`, `PERMISSION_REQUIRED`, or `COMMAND_DENIED`, do not retry with a shell wrapper or more complex syntax. Continue with the best available file/tool work and summarize the manual verification step.
|
|
69
|
+
- If a denied command was an `rm` or `rmdir` deletion attempt, do not retry with a broader or more complex shell form. Use `delete_file` / `delete_directory` structured tools when that can express the requested targets, or explain the exact safety blocker.
|
|
70
|
+
- In the final answer, do not say build, compile, test, validation, or execution passed unless a successful current-turn command result proves it. If validation was not run or failed, say that plainly.
|
|
71
|
+
|
|
72
|
+
Safety:
|
|
73
|
+
- Never attempt to read secrets such as `.env` files, private keys, tokens, cookies, local credentials, or credential-like hidden files.
|
|
74
|
+
- Do not claim you cannot access local files when a suitable read-only tool is available.
|
|
75
|
+
- After using tools, produce a user-facing answer. Do not end the turn with raw tool output only.
|