@axplusb/kepler 1.0.4 → 1.0.9
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/KEPLER-README.md +128 -2
- package/package.json +4 -4
- package/pulse/app/activity/page.tsx +1 -1
- package/pulse/app/api/import/route.ts +1 -1
- package/pulse/app/api/memory/route.ts +2 -2
- package/pulse/app/costs/page.tsx +1 -1
- package/pulse/app/export/page.tsx +3 -3
- package/pulse/app/globals.css +3 -3
- package/pulse/app/help/page.tsx +11 -11
- package/pulse/app/history/page.tsx +2 -2
- package/pulse/app/layout.tsx +2 -2
- package/pulse/app/memory/page.tsx +2 -2
- package/pulse/app/overview-client.tsx +1 -1
- package/pulse/app/page.tsx +2 -2
- package/pulse/app/plans/page.tsx +2 -2
- package/pulse/app/projects/page.tsx +1 -1
- package/pulse/app/sessions/page.tsx +1 -1
- package/pulse/app/settings/page.tsx +4 -4
- package/pulse/app/todos/page.tsx +2 -2
- package/pulse/app/tools/page.tsx +1 -1
- package/pulse/cli.js +15 -25
- package/pulse/components/layout/sidebar.tsx +2 -2
- package/pulse/components/sessions/replay/user-tool-result.tsx +1 -1
- package/pulse/lib/claude-reader.ts +1 -1
- package/pulse/lib/decode.ts +1 -1
- package/pulse/package.json +3 -3
- package/src/auth/tarang-auth.mjs +1 -1
- package/src/config/cli-args.mjs +5 -0
- package/src/context/retriever.mjs +1 -1
- package/src/context/skeleton.mjs +1 -1
- package/src/core/approval.mjs +22 -53
- package/src/core/headless.mjs +68 -24
- package/src/core/paths.mjs +1 -1
- package/src/core/project-artifacts.mjs +37 -0
- package/src/core/stream-client.mjs +6 -1
- package/src/core/tool-executor.mjs +163 -55
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +217 -112
- package/src/terminal/main.mjs +19 -1
- package/src/terminal/repl.mjs +40 -105
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +82 -0
- package/src/tools/bash.mjs +5 -2
- package/src/tools/project-overview.mjs +418 -0
- package/src/tools/registry.mjs +0 -16
- package/src/ui/banner.mjs +7 -14
- package/src/ui/formatter.mjs +6 -40
- package/README.md.orca +0 -82
package/pulse/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "1.0.
|
|
2
|
+
"name": "kepler-pulse",
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": true,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Kepler Pulse — real-time analytics dashboard for Kepler AI agent sessions",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "next dev",
|
|
8
8
|
"build": "next build",
|
package/src/auth/tarang-auth.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import * as http from 'node:http';
|
|
|
10
10
|
import { getLoginSuccessHTML } from '../ui/banner.mjs';
|
|
11
11
|
import { resolveBackendUrl } from '../core/backend-url.mjs';
|
|
12
12
|
|
|
13
|
-
const CONFIG_DIR = process.env.KEPLER_HOME ||
|
|
13
|
+
const CONFIG_DIR = process.env.KEPLER_HOME || path.join(os.homedir(), '.kepler');
|
|
14
14
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
15
15
|
|
|
16
16
|
export class TarangAuth {
|
package/src/config/cli-args.mjs
CHANGED
|
@@ -26,6 +26,7 @@ export function parseArgs(args) {
|
|
|
26
26
|
systemPrompt: null,
|
|
27
27
|
addDirs: [],
|
|
28
28
|
maxTurns: null,
|
|
29
|
+
timeout: null,
|
|
29
30
|
allowedTools: null,
|
|
30
31
|
disallowedTools: null,
|
|
31
32
|
resume: false,
|
|
@@ -72,6 +73,10 @@ export function parseArgs(args) {
|
|
|
72
73
|
result.maxTurns = parseInt(args[++i], 10);
|
|
73
74
|
break;
|
|
74
75
|
|
|
76
|
+
case '--timeout':
|
|
77
|
+
result.timeout = parseInt(args[++i], 10);
|
|
78
|
+
break;
|
|
79
|
+
|
|
75
80
|
case '--allowedTools':
|
|
76
81
|
result.allowedTools = args[++i]?.split(',').map(s => s.trim());
|
|
77
82
|
break;
|
|
@@ -8,7 +8,7 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { indexDir as getIndexDir } from '../core/paths.mjs';
|
|
10
10
|
|
|
11
|
-
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.
|
|
11
|
+
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.kepler', '__pycache__', '.venv', 'venv', 'dist', 'build', '.next']);
|
|
12
12
|
const CODE_EXTS = new Set(['.js', '.mjs', '.ts', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.php', '.c', '.cpp', '.h', '.css', '.html', '.json', '.yaml', '.yml', '.toml', '.md', '.sh']);
|
|
13
13
|
const MAX_FILE_SIZE = 100_000; // 100KB
|
|
14
14
|
const CHUNK_LINES = 50;
|
package/src/context/skeleton.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
|
|
15
|
-
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.
|
|
15
|
+
const IGNORED_DIRS = new Set(['.git', 'node_modules', '.kepler', '__pycache__', '.venv', 'venv', 'dist', 'build', '.next', '.cache', 'coverage', '.tox']);
|
|
16
16
|
const CODE_EXTS = new Set(['.js', '.mjs', '.ts', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.c', '.cpp', '.h']);
|
|
17
17
|
const MAX_FILE_SIZE = 200_000;
|
|
18
18
|
|
package/src/core/approval.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Shell commands are risk-assessed (safe/medium/high).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import { toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
|
|
14
14
|
|
|
15
15
|
// ── Tool Classification ──
|
|
16
16
|
|
|
@@ -66,44 +66,6 @@ const WHITE = '\x1b[37m';
|
|
|
66
66
|
|
|
67
67
|
const write = (s) => process.stderr.write(s);
|
|
68
68
|
|
|
69
|
-
// ── Tool Description ──
|
|
70
|
-
|
|
71
|
-
function shortPath(p) {
|
|
72
|
-
if (!p) return '';
|
|
73
|
-
const cwd = process.cwd();
|
|
74
|
-
return p.startsWith(cwd) ? p.slice(cwd.length + 1) : p;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function toolSummary(toolName, args) {
|
|
78
|
-
switch (toolName) {
|
|
79
|
-
case 'shell':
|
|
80
|
-
return args.command || '(empty)';
|
|
81
|
-
case 'write_file':
|
|
82
|
-
return shortPath(args.path || args.file_path || '');
|
|
83
|
-
case 'write_project': {
|
|
84
|
-
const files = args.files || [];
|
|
85
|
-
if (files.length === 0) return 'batch write';
|
|
86
|
-
return files.map(f => shortPath(f.path || f.file_path || '')).join(', ');
|
|
87
|
-
}
|
|
88
|
-
case 'edit_file': {
|
|
89
|
-
const fp = shortPath(args.path || args.file_path || '');
|
|
90
|
-
const search = (args.search || '').slice(0, 30);
|
|
91
|
-
return search ? `${fp} "${search}..."` : fp;
|
|
92
|
-
}
|
|
93
|
-
case 'delete_file':
|
|
94
|
-
return shortPath(args.path || args.file_path || '');
|
|
95
|
-
case 'read_file':
|
|
96
|
-
case 'read_files':
|
|
97
|
-
return shortPath(args.file_path || args.path || (args.file_paths || [])[0] || '');
|
|
98
|
-
case 'search_code':
|
|
99
|
-
return `"${args.query || args.pattern || ''}"`;
|
|
100
|
-
case 'list_files':
|
|
101
|
-
return args.pattern || args.path || '';
|
|
102
|
-
default:
|
|
103
|
-
return Object.values(args || {}).filter(v => typeof v === 'string').join(', ').slice(0, 50) || '';
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
69
|
// ── Approval Manager ──
|
|
108
70
|
|
|
109
71
|
export class ApprovalManager {
|
|
@@ -138,7 +100,7 @@ export class ApprovalManager {
|
|
|
138
100
|
return `${DIM}ask${RST}`;
|
|
139
101
|
}
|
|
140
102
|
|
|
141
|
-
async check(toolName, args, requireApproval = false) {
|
|
103
|
+
async check(toolName, args, requireApproval = false, context = {}) {
|
|
142
104
|
if (this.planMode && WRITE_TOOLS.has(toolName)) {
|
|
143
105
|
return { approved: false, reason: `Blocked by plan mode: ${toolName}` };
|
|
144
106
|
}
|
|
@@ -157,11 +119,11 @@ export class ApprovalManager {
|
|
|
157
119
|
return { approved: true };
|
|
158
120
|
}
|
|
159
121
|
if (FORCE_APPROVAL_SHELL.some(p => p.test(args.command || ''))) {
|
|
160
|
-
return this._prompt(toolName, args);
|
|
122
|
+
return this._prompt(toolName, args, context);
|
|
161
123
|
}
|
|
162
124
|
}
|
|
163
125
|
if (NEVER_AUTO_APPROVE.has(toolName)) {
|
|
164
|
-
return this._prompt(toolName, args);
|
|
126
|
+
return this._prompt(toolName, args, context);
|
|
165
127
|
}
|
|
166
128
|
if (this.approveAll) {
|
|
167
129
|
this.history.push({ tool: toolName, decision: 'auto', time: Date.now() });
|
|
@@ -171,20 +133,27 @@ export class ApprovalManager {
|
|
|
171
133
|
this.history.push({ tool: toolName, decision: 'type-auto', time: Date.now() });
|
|
172
134
|
return { approved: true };
|
|
173
135
|
}
|
|
174
|
-
return this._prompt(toolName, args);
|
|
136
|
+
return this._prompt(toolName, args, context);
|
|
175
137
|
}
|
|
176
138
|
|
|
177
|
-
async _prompt(toolName, args) {
|
|
139
|
+
async _prompt(toolName, args, context = {}) {
|
|
178
140
|
const baseRisk = RISK_LEVELS[toolName] || 'medium';
|
|
179
|
-
const
|
|
180
|
-
const
|
|
141
|
+
const assessedRisk = toolName === 'shell' ? assessShellRisk(args.command) : baseRisk;
|
|
142
|
+
const risk = context.risk || assessedRisk;
|
|
143
|
+
const label = toolDisplayLabel(toolName);
|
|
144
|
+
const summary = toolDisplaySummary(toolName, args);
|
|
181
145
|
const isDestructive = risk === 'high';
|
|
182
146
|
|
|
183
|
-
|
|
147
|
+
write(`\n ${isDestructive ? `${YELLOW}⚠${RST}` : `${CYAN}?${RST}`} ${BOLD}Approval required${RST}\n`);
|
|
148
|
+
write(` ${GRAY}Action${RST} ${WHITE}${label}${RST}\n`);
|
|
149
|
+
if (summary) write(` ${GRAY}Target${RST} ${WHITE}${summary.slice(0, 160)}${RST}\n`);
|
|
150
|
+
write(` ${GRAY}Risk${RST} ${isDestructive ? YELLOW : CYAN}${risk}${RST}\n`);
|
|
151
|
+
if (context.reason) write(` ${GRAY}Reason${RST} ${DIM}${String(context.reason).slice(0, 160)}${RST}\n`);
|
|
152
|
+
|
|
184
153
|
if (isDestructive) {
|
|
185
|
-
write(` ${
|
|
154
|
+
write(` ${DIM}Choose${RST} ${WHITE}[y]${RST} allow once ${WHITE}[n]${RST} deny ${WHITE}[d]${RST} details\n`);
|
|
186
155
|
} else {
|
|
187
|
-
write(` ${DIM}
|
|
156
|
+
write(` ${DIM}Choose${RST} ${WHITE}[y]${RST} once ${WHITE}[n]${RST} deny ${WHITE}[t]${RST} this action ${WHITE}[a]${RST} all ${WHITE}[d]${RST} details\n`);
|
|
188
157
|
}
|
|
189
158
|
|
|
190
159
|
const key = await this._readKey();
|
|
@@ -206,7 +175,7 @@ export class ApprovalManager {
|
|
|
206
175
|
|
|
207
176
|
case 'a':
|
|
208
177
|
case 'A':
|
|
209
|
-
if (isDestructive) return this._prompt(toolName, args);
|
|
178
|
+
if (isDestructive) return this._prompt(toolName, args, context);
|
|
210
179
|
this.approveAll = true;
|
|
211
180
|
write(` ${GREEN}✓✓${RST} ${DIM}allow-all activated${RST}\n\n`);
|
|
212
181
|
this.history.push({ tool: toolName, decision: 'approve-all', time: Date.now() });
|
|
@@ -214,7 +183,7 @@ export class ApprovalManager {
|
|
|
214
183
|
|
|
215
184
|
case 't':
|
|
216
185
|
case 'T':
|
|
217
|
-
if (isDestructive) return this._prompt(toolName, args);
|
|
186
|
+
if (isDestructive) return this._prompt(toolName, args, context);
|
|
218
187
|
this.approvedToolTypes.add(toolName);
|
|
219
188
|
write(` ${GREEN}✓${RST} ${DIM}always allow ${toolName}${RST}\n\n`);
|
|
220
189
|
this.history.push({ tool: toolName, decision: 'type-approve', time: Date.now() });
|
|
@@ -223,10 +192,10 @@ export class ApprovalManager {
|
|
|
223
192
|
case 'd':
|
|
224
193
|
case 'D':
|
|
225
194
|
write(`\n${DIM}${JSON.stringify(args, null, 2)}${RST}\n\n`);
|
|
226
|
-
return this._prompt(toolName, args);
|
|
195
|
+
return this._prompt(toolName, args, context);
|
|
227
196
|
|
|
228
197
|
default:
|
|
229
|
-
return this._prompt(toolName, args);
|
|
198
|
+
return this._prompt(toolName, args, context);
|
|
230
199
|
}
|
|
231
200
|
}
|
|
232
201
|
|
package/src/core/headless.mjs
CHANGED
|
@@ -13,10 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import { TarangStreamClient } from './stream-client.mjs';
|
|
15
15
|
import { createToolExecutor } from './tool-executor.mjs';
|
|
16
|
+
import { persistProjectArtifacts } from './project-artifacts.mjs';
|
|
16
17
|
import { TarangAuth } from '../auth/tarang-auth.mjs';
|
|
17
18
|
import { ApprovalManager } from './approval.mjs';
|
|
18
|
-
import { ContextRetriever } from '../context/retriever.mjs';
|
|
19
|
-
import { buildProjectSkeleton } from '../context/skeleton.mjs';
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* Run a single instruction in headless mode.
|
|
@@ -46,22 +45,8 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
46
45
|
process.exit(1);
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
const retriever = new ContextRetriever(process.cwd());
|
|
52
|
-
try {
|
|
53
|
-
const indexPromise = retriever.buildIndex();
|
|
54
|
-
const indexTimeout = new Promise((_, reject) =>
|
|
55
|
-
setTimeout(() => reject(new Error('Index timeout')), 15000)
|
|
56
|
-
);
|
|
57
|
-
await Promise.race([indexPromise, indexTimeout]);
|
|
58
|
-
log('Index ready');
|
|
59
|
-
} catch (e) {
|
|
60
|
-
log(`Index skipped: ${e.message || 'failed'}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const skeleton = buildProjectSkeleton(process.cwd());
|
|
64
|
-
const toolExecutor = createToolExecutor({ retriever });
|
|
48
|
+
// Projects are registered and indexed only when the agent requests an overview.
|
|
49
|
+
const toolExecutor = createToolExecutor();
|
|
65
50
|
|
|
66
51
|
// Auto-approve everything — no prompts
|
|
67
52
|
const approval = new ApprovalManager({ autoApprove: true });
|
|
@@ -85,14 +70,24 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
85
70
|
// ── Execute ──
|
|
86
71
|
emit({ type: 'start', timestamp: Date.now(), instruction, model: model || 'default', cwd: process.cwd() });
|
|
87
72
|
|
|
88
|
-
const execContext = {
|
|
89
|
-
|
|
73
|
+
const execContext = {
|
|
74
|
+
cwd: process.cwd(),
|
|
75
|
+
freeswim: true,
|
|
76
|
+
project_resources: toolExecutor.getProjectResources(),
|
|
77
|
+
agent_context: toolExecutor.getAgentContext(),
|
|
78
|
+
};
|
|
90
79
|
if (model) execContext.model_override = model;
|
|
91
80
|
|
|
92
81
|
let toolCount = 0;
|
|
93
82
|
let finalContent = '';
|
|
94
83
|
let totalCost = 0;
|
|
95
84
|
|
|
85
|
+
// ── Telemetry collectors ──
|
|
86
|
+
const toolCalls = []; // { tool, duration_ms, success }
|
|
87
|
+
const subAgents = []; // { type, model, duration_s, tool_calls, success }
|
|
88
|
+
let stagnationCount = 0;
|
|
89
|
+
let usage = {}; // { input_tokens, output_tokens, cache_read, cache_write }
|
|
90
|
+
|
|
96
91
|
try {
|
|
97
92
|
for await (const event of client.execute(instruction, execContext)) {
|
|
98
93
|
const { type, data } = event;
|
|
@@ -101,6 +96,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
101
96
|
toolCount++;
|
|
102
97
|
const toolName = data?.tool || 'unknown';
|
|
103
98
|
const args = data?.args || {};
|
|
99
|
+
toolCalls.push({ tool: toolName, success: null, duration_ms: 0 });
|
|
104
100
|
emit({ type: 'tool_call', tool: toolName, args, approved: true });
|
|
105
101
|
log(`Tool: ${toolName}`);
|
|
106
102
|
}
|
|
@@ -108,9 +104,42 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
108
104
|
if (type === 'tool_result' || type === 'tool_done') {
|
|
109
105
|
const success = data?.success !== false;
|
|
110
106
|
const duration = data?.duration_s || 0;
|
|
107
|
+
// Update last tool call with result
|
|
108
|
+
const last = toolCalls.findLast(t => t.tool === (data?.tool || ''));
|
|
109
|
+
if (last) { last.success = success; last.duration_ms = Math.round(duration * 1000); }
|
|
111
110
|
emit({ type: 'tool_result', tool: data?.tool || '', success, duration_ms: Math.round(duration * 1000) });
|
|
112
111
|
}
|
|
113
112
|
|
|
113
|
+
if (type === 'sub_agent_start') {
|
|
114
|
+
log(`SubAgent: ${data?.type} (${data?.model})`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (type === 'sub_agent_complete') {
|
|
118
|
+
subAgents.push({
|
|
119
|
+
type: data?.type || '',
|
|
120
|
+
model: data?.model || '',
|
|
121
|
+
duration_s: data?.duration_s || 0,
|
|
122
|
+
tool_calls: data?.tool_calls || 0,
|
|
123
|
+
success: data?.success !== false,
|
|
124
|
+
});
|
|
125
|
+
emit({ type: 'sub_agent', ...data });
|
|
126
|
+
log(`SubAgent done: ${data?.type} (${data?.tool_calls} tools, ${data?.duration_s}s)`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (type === 'plan_created' || type === 'goal_created') {
|
|
130
|
+
persistProjectArtifacts(
|
|
131
|
+
data,
|
|
132
|
+
toolExecutor.getProjectResources(),
|
|
133
|
+
log,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (type === 'stagnation' || type === 'stagnation_detected') {
|
|
138
|
+
stagnationCount++;
|
|
139
|
+
emit({ type: 'stagnation', reason: data?.reason || '', strategy: data?.recovery_strategy || '' });
|
|
140
|
+
log(`Stagnation: ${data?.reason || ''}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
114
143
|
if (type === 'content') {
|
|
115
144
|
finalContent = data?.text || '';
|
|
116
145
|
}
|
|
@@ -122,10 +151,15 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
122
151
|
|
|
123
152
|
if (type === 'complete') {
|
|
124
153
|
totalCost = data?.cost || data?.total_cost || 0;
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
if (data?.usage
|
|
128
|
-
|
|
154
|
+
if (data?.usage?.total_cost) totalCost = data.usage.total_cost;
|
|
155
|
+
// Capture token usage
|
|
156
|
+
if (data?.usage) {
|
|
157
|
+
usage = {
|
|
158
|
+
input_tokens: data.usage.total_input_tokens || data.usage.input_tokens || 0,
|
|
159
|
+
output_tokens: data.usage.total_output_tokens || data.usage.output_tokens || 0,
|
|
160
|
+
cache_read: data.usage.cache_read_input_tokens || data.usage.cache_read || 0,
|
|
161
|
+
cache_write: data.usage.cache_creation_input_tokens || data.usage.cache_write || 0,
|
|
162
|
+
};
|
|
129
163
|
}
|
|
130
164
|
}
|
|
131
165
|
|
|
@@ -148,9 +182,19 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
148
182
|
|
|
149
183
|
const durationS = (Date.now() - startTime) / 1000;
|
|
150
184
|
|
|
185
|
+
// ── Tool breakdown ──
|
|
186
|
+
const toolBreakdown = {};
|
|
187
|
+
for (const t of toolCalls) {
|
|
188
|
+
toolBreakdown[t.tool] = (toolBreakdown[t.tool] || 0) + 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
151
191
|
emit({
|
|
152
192
|
type: 'complete',
|
|
153
193
|
tools: toolCount,
|
|
194
|
+
tool_breakdown: toolBreakdown,
|
|
195
|
+
sub_agents: subAgents,
|
|
196
|
+
stagnation_triggers: stagnationCount,
|
|
197
|
+
usage,
|
|
154
198
|
duration_s: Math.round(durationS * 10) / 10,
|
|
155
199
|
cost_usd: totalCost,
|
|
156
200
|
model: model || 'default',
|
package/src/core/paths.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import * as path from 'node:path';
|
|
|
22
22
|
import * as os from 'node:os';
|
|
23
23
|
import * as crypto from 'node:crypto';
|
|
24
24
|
|
|
25
|
-
const KEPLER_HOME = process.env.KEPLER_HOME ||
|
|
25
|
+
const KEPLER_HOME = process.env.KEPLER_HOME || path.join(os.homedir(), '.kepler');
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Hash a project path to a short directory name.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Persist generated session artifacts only beneath roots registered by the CLI.
|
|
6
|
+
*/
|
|
7
|
+
export function persistProjectArtifacts(data, resources, log = () => {}) {
|
|
8
|
+
const projectIds = new Set(
|
|
9
|
+
Array.isArray(data?.project_ids) ? data.project_ids.filter(Boolean) : []
|
|
10
|
+
);
|
|
11
|
+
const registered = Array.isArray(resources) ? resources : [];
|
|
12
|
+
const targets = projectIds.size > 0
|
|
13
|
+
? registered.filter(resource => projectIds.has(resource.project_id))
|
|
14
|
+
: registered.length === 1 ? registered : [];
|
|
15
|
+
const artifacts = [
|
|
16
|
+
['goal', 'goal.md'],
|
|
17
|
+
['plan', 'plan.md'],
|
|
18
|
+
].filter(([field]) => typeof data?.[field] === 'string' && data[field].trim());
|
|
19
|
+
const written = [];
|
|
20
|
+
|
|
21
|
+
for (const resource of targets) {
|
|
22
|
+
for (const [field, filename] of artifacts) {
|
|
23
|
+
try {
|
|
24
|
+
const keplerDir = path.join(resource.root, '.kepler');
|
|
25
|
+
fs.mkdirSync(keplerDir, { recursive: true });
|
|
26
|
+
const artifactPath = path.join(keplerDir, filename);
|
|
27
|
+
fs.writeFileSync(artifactPath, data[field], 'utf-8');
|
|
28
|
+
resource[field] = data[field];
|
|
29
|
+
written.push(artifactPath);
|
|
30
|
+
log(`${field} saved -> ${artifactPath}`);
|
|
31
|
+
} catch {
|
|
32
|
+
// Persistence is helpful but must not stop the agent.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return written;
|
|
37
|
+
}
|
|
@@ -290,7 +290,12 @@ export class TarangStreamClient {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
// Use the same ApprovalManager for consistent UX
|
|
293
|
-
const { approved, reason: denyReason } = await this.approval.check(
|
|
293
|
+
const { approved, reason: denyReason } = await this.approval.check(
|
|
294
|
+
tool,
|
|
295
|
+
args || {},
|
|
296
|
+
true,
|
|
297
|
+
{ risk, reason },
|
|
298
|
+
);
|
|
294
299
|
|
|
295
300
|
// Map ApprovalManager decision to framework scope
|
|
296
301
|
let decision, scope;
|