glitool 2.0.2 → 2.0.4
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/agent.js +35 -7
- package/dist/agents/coder.js +8 -1
- package/dist/agents/explainer.js +10 -6
- package/dist/agents/judge.js +2 -2
- package/dist/agents/planner.js +23 -8
- package/dist/agents/planningAgent.js +19 -4
- package/dist/agents/reviewer-agent.js +12 -16
- package/dist/llm/classifier.js +8 -1
- package/dist/llm/factory.js +9 -5
- package/dist/memory.js +8 -1
- package/dist/monitor.js +9 -0
- package/dist/projectMemory.js +24 -9
- package/dist/ui/App.js +1 -1
- package/dist/ui/StatusBar.js +11 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -19,8 +19,8 @@ import { runPlanningAgent } from "./agents/planningAgent.js";
|
|
|
19
19
|
import { runDebugger } from "./agents/debugger.js";
|
|
20
20
|
import { runRefactorer } from "./agents/refactorer.js";
|
|
21
21
|
import { runGitAgent } from "./agents/git-agent.js";
|
|
22
|
-
import { ToolMessage } from "@langchain/core/messages";
|
|
23
22
|
import { makeLlm, startNewRequest } from './llm/factory.js';
|
|
23
|
+
import { emit } from './monitor.js';
|
|
24
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
25
|
const __dirname = dirname(__filename);
|
|
26
26
|
loadEnv({ path: join(os.homedir(), '.glitool', '.env') });
|
|
@@ -59,14 +59,29 @@ function buildSystemPrompt() {
|
|
|
59
59
|
summary = loadSummary();
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
let prompt = `You are an
|
|
62
|
+
let prompt = `You are Glitool — an AI coding assistant running in the user's terminal.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
64
|
+
You can:
|
|
65
|
+
- Have a normal conversation (just respond, no tools needed)
|
|
66
|
+
- Answer programming and general questions
|
|
67
|
+
- Read, write, edit, and search files in the user's project
|
|
68
|
+
- Run shell commands, fetch web pages
|
|
69
|
+
- Plan, review, refactor, debug code, and help with git
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
Be concise. Default to plain conversation. Only call tools when the request clearly needs them.
|
|
72
|
+
|
|
73
|
+
When the user asks to read, show, or display a specific file → call readFile.
|
|
74
|
+
For "read <name>" shorthand, pass the bare name; the tool searches the project automatically.
|
|
75
|
+
Don't claim a file is missing without verifying via listFiles or readFile first.
|
|
76
|
+
|
|
77
|
+
If any tool returns USER_CANCELLED, stop immediately and tell the user. Never retry a cancelled operation.
|
|
78
|
+
|
|
79
|
+
Style:
|
|
80
|
+
- No preamble like "Sure!", "Of course!", "I'd be happy to..."
|
|
81
|
+
- Code blocks use language tags: \`\`\`ts, \`\`\`py, \`\`\`bash
|
|
82
|
+
- File references use backticks: \`src/auth.ts:42\`
|
|
83
|
+
- The user's current working directory IS the project they're working on — file paths are relative to it.
|
|
84
|
+
`;
|
|
70
85
|
if (summary) {
|
|
71
86
|
const capped = summary.length > MAX_SUMMARY_CHARS
|
|
72
87
|
? summary.slice(0, MAX_SUMMARY_CHARS) + '\n…[summary truncated]'
|
|
@@ -180,7 +195,9 @@ function extractTarget(args) {
|
|
|
180
195
|
}
|
|
181
196
|
export async function chat(userInput, onToolCall, onStatus, onToken, onEscalation, onUsage, onStageEvent) {
|
|
182
197
|
startNewRequest();
|
|
198
|
+
emit('user_prompt', { text: userInput });
|
|
183
199
|
const decision = await route(userInput, sessionMessages.slice(-6));
|
|
200
|
+
emit('router', { domain: decision.domain, tier: decision.tier, model: decision.recommendedModel, reason: decision.reason });
|
|
184
201
|
logRouting(userInput, decision);
|
|
185
202
|
const cleanedInput = decision.source === 'explicit' ? stripExplicitPrefix(userInput) : userInput;
|
|
186
203
|
sessionMessages.push(new HumanMessage(cleanedInput));
|
|
@@ -191,6 +208,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
191
208
|
return shortcut;
|
|
192
209
|
}
|
|
193
210
|
if (decision.domain === 'planning') {
|
|
211
|
+
emit('agent', { name: 'planning' });
|
|
194
212
|
onStatus?.('Planning...');
|
|
195
213
|
const result = await runPlanningAgent(cleanedInput, (inputTokens, outputTokens) => {
|
|
196
214
|
onUsage?.(inputTokens + outputTokens, estimateCost('gpt-5.4', inputTokens, outputTokens));
|
|
@@ -200,6 +218,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
200
218
|
return result;
|
|
201
219
|
}
|
|
202
220
|
if (decision.domain === 'review') {
|
|
221
|
+
emit('agent', { name: 'reviewer' });
|
|
203
222
|
onStageEvent?.({ type: 'stage_start', stage: 'reviewer' });
|
|
204
223
|
const result = await runReviewer(cleanedInput, (name, args) => {
|
|
205
224
|
onStageEvent?.({ type: 'tool', stage: 'reviewer', tool: name, target: extractTarget(args) });
|
|
@@ -211,6 +230,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
211
230
|
return result;
|
|
212
231
|
}
|
|
213
232
|
if (decision.domain === 'debugging') {
|
|
233
|
+
emit('agent', { name: 'debugger' });
|
|
214
234
|
onStageEvent?.({ type: 'stage_start', stage: 'debugger' });
|
|
215
235
|
const result = await runDebugger(cleanedInput, (name, args) => {
|
|
216
236
|
onStageEvent?.({ type: 'tool', stage: 'debugger', tool: name, target: extractTarget(args) });
|
|
@@ -222,6 +242,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
222
242
|
return result;
|
|
223
243
|
}
|
|
224
244
|
if (decision.domain === 'refactoring') {
|
|
245
|
+
emit('agent', { name: 'refactorer' });
|
|
225
246
|
onStageEvent?.({ type: 'stage_start', stage: 'refactorer' });
|
|
226
247
|
const result = await runRefactorer(cleanedInput, (name, args) => {
|
|
227
248
|
onStageEvent?.({ type: 'tool', stage: 'refactorer', tool: name, target: extractTarget(args) });
|
|
@@ -233,6 +254,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
233
254
|
return result;
|
|
234
255
|
}
|
|
235
256
|
if (decision.domain === 'git') {
|
|
257
|
+
emit('agent', { name: 'git' });
|
|
236
258
|
onStageEvent?.({ type: 'stage_start', stage: 'git_agent' });
|
|
237
259
|
const result = await runGitAgent(cleanedInput, (name, args) => {
|
|
238
260
|
onStageEvent?.({ type: 'tool', stage: 'git_agent', tool: name, target: extractTarget(args) });
|
|
@@ -244,6 +266,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
244
266
|
return result;
|
|
245
267
|
}
|
|
246
268
|
if (decision.domain === 'coding') {
|
|
269
|
+
emit('agent', { name: 'coder' });
|
|
247
270
|
const graphResult = await runAgentGraph(cleanedInput, buildSystemPrompt(), onToolCall, onStatus ?? (() => { }), decision, onStageEvent // ← add this
|
|
248
271
|
);
|
|
249
272
|
if (graphResult.escalated && onEscalation) {
|
|
@@ -260,6 +283,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
260
283
|
return graphResult.finalOutput;
|
|
261
284
|
}
|
|
262
285
|
}
|
|
286
|
+
emit('agent', { name: 'chat' });
|
|
263
287
|
const simpleAgent = createReactAgent({
|
|
264
288
|
llm: createLlm(decision.recommendedModel),
|
|
265
289
|
tools,
|
|
@@ -292,12 +316,14 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
292
316
|
}
|
|
293
317
|
if (event === 'on_tool_start') {
|
|
294
318
|
onToolCall(eventName, data.input);
|
|
319
|
+
emit('tool_call', { name: eventName, input: data.input });
|
|
295
320
|
}
|
|
296
321
|
if (event === 'on_chat_model_end') {
|
|
297
322
|
const usage = data.output?.usage_metadata;
|
|
298
323
|
if (usage) {
|
|
299
324
|
totalInputTokens += usage.input_tokens ?? 0;
|
|
300
325
|
totalOutputTokens += usage.output_tokens ?? 0;
|
|
326
|
+
emit('llm_call', { tokens_in: usage.input_tokens ?? 0, tokens_out: usage.output_tokens ?? 0 });
|
|
301
327
|
}
|
|
302
328
|
if (!finalResponse) {
|
|
303
329
|
const output = data.output;
|
|
@@ -316,5 +342,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
316
342
|
onUsage(totalInputTokens + totalOutputTokens, estimateCost(model, totalInputTokens, totalOutputTokens));
|
|
317
343
|
}
|
|
318
344
|
saveSession(sessionMessages);
|
|
345
|
+
emit('response', { text: finalResponse });
|
|
346
|
+
emit('done', { total_tokens: totalInputTokens + totalOutputTokens });
|
|
319
347
|
return finalResponse;
|
|
320
348
|
}
|
package/dist/agents/coder.js
CHANGED
|
@@ -22,7 +22,14 @@ GROUNDING RULES — these are not optional:
|
|
|
22
22
|
6. Maximum 5 file reads per task. If you need more, you're doing it wrong — use searchCode instead.
|
|
23
23
|
7. If you can't safely complete the task, STOP and return a failure message. Do not invent.
|
|
24
24
|
|
|
25
|
-
Be surgical, not exhaustive. Most tasks need 2-4 tool calls, not 15. The validator will catch broken output — you don't need to over-verify
|
|
25
|
+
Be surgical, not exhaustive. Most tasks need 2-4 tool calls, not 15. The validator will catch broken output — you don't need to over-verify.
|
|
26
|
+
|
|
27
|
+
Response style:
|
|
28
|
+
- Your final text should be 1-3 sentences summarizing what files you changed and why.
|
|
29
|
+
- Do NOT paste file contents in the response — the files are on disk; the user can read them.
|
|
30
|
+
- The validator runs tsc + ESLint after you finish — no need to verify those yourself.
|
|
31
|
+
- If a step is impossible (binary file, command blocked, etc.), say so explicitly and stop.
|
|
32
|
+
`)
|
|
26
33
|
});
|
|
27
34
|
const stream = await coderAgent.stream({ messages: [new HumanMessage(`Plan to execute:\n${plan}\n\nOriginal request: ${userMessage}`)] }, { recursionLimit: 60, streamMode: 'updates' });
|
|
28
35
|
let result = '';
|
package/dist/agents/explainer.js
CHANGED
|
@@ -5,12 +5,16 @@ export async function explainResponse(response) {
|
|
|
5
5
|
return '';
|
|
6
6
|
const explainerLlm = makeLlm('meta-llama/Llama-3.3-70B-Instruct-Turbo');
|
|
7
7
|
const result = await explainerLlm.invoke([
|
|
8
|
-
new SystemMessage(`You are a coding teacher.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
new SystemMessage(`You are a friendly coding teacher.
|
|
9
|
+
|
|
10
|
+
Given what an AI coding assistant just did (described below), explain it in 2-4 simple sentences that a beginner would understand.
|
|
11
|
+
|
|
12
|
+
Focus on:
|
|
13
|
+
- WHAT changed and WHY
|
|
14
|
+
- WHICH concept was used (e.g., "promise chaining", "dependency injection")
|
|
15
|
+
- ONE thing to learn next
|
|
16
|
+
|
|
17
|
+
Tone: encouraging, plain language, define any jargon used.`),
|
|
14
18
|
new HumanMessage(`What was just done:\n${response}`)
|
|
15
19
|
]);
|
|
16
20
|
return result.content;
|
package/dist/agents/judge.js
CHANGED
|
@@ -23,7 +23,7 @@ IMPORTANT — how the coder works:
|
|
|
23
23
|
Return JSON only:
|
|
24
24
|
{
|
|
25
25
|
"verdict": "ok" or "fail",
|
|
26
|
-
|
|
26
|
+
"failure_point": "plan" | "workflow" | "execution" | "final_output" | null,
|
|
27
27
|
"failure_step_id": number or null,
|
|
28
28
|
"reason": "short explanation",
|
|
29
29
|
"fix_hint": "specific instruction to fix the problem, empty if ok",
|
|
@@ -34,7 +34,7 @@ Return JSON only:
|
|
|
34
34
|
failure_point meanings:
|
|
35
35
|
- "plan": the plan itself was wrong or incomplete — needs replanning
|
|
36
36
|
- "workflow": wrong execution order caused the failure
|
|
37
|
-
- "
|
|
37
|
+
- "execution": a specific step failed — use failure_step_id
|
|
38
38
|
- "final_output": code runs but doesn't meet the requirement
|
|
39
39
|
- null: everything ok, verdict must be ok`),
|
|
40
40
|
new HumanMessage(`User request: ${input.userMessage}\n\n` +
|
package/dist/agents/planner.js
CHANGED
|
@@ -5,15 +5,30 @@ export async function runPlanner(userMessage, context, model) {
|
|
|
5
5
|
const response = await llm.invoke([
|
|
6
6
|
new SystemMessage(`You are a coding task planner. Output a structured JSON plan.
|
|
7
7
|
|
|
8
|
+
Output exactly one of:
|
|
9
|
+
|
|
10
|
+
A) The literal text: SIMPLE
|
|
11
|
+
(use when the task is just a question, explanation, or chat — no file changes or code execution needed)
|
|
12
|
+
|
|
13
|
+
B) A JSON array of 3-6 steps:
|
|
14
|
+
[
|
|
15
|
+
{ "id": 1, "action": "read", "target": "src/auth.ts", "depends_on": [], "why": "understand current login flow" },
|
|
16
|
+
{ "id": 2, "action": "edit", "target": "src/auth.ts", "depends_on": [1], "why": "add refresh-token check before validate" },
|
|
17
|
+
{ "id": 3, "action": "run", "target": "npx tsc --noEmit","depends_on": [2], "why": "verify it compiles" }
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
Action values:
|
|
21
|
+
- "read": open a file to understand it
|
|
22
|
+
- "edit": modify an existing file
|
|
23
|
+
- "create": make a new file
|
|
24
|
+
- "run": execute a shell command (tsc, npm, git, etc.)
|
|
25
|
+
- "search": grep the codebase
|
|
26
|
+
|
|
8
27
|
Rules:
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
- Be specific about
|
|
13
|
-
- Do NOT write any code — only plan the steps
|
|
14
|
-
- Keep it to 3-6 steps maximum
|
|
15
|
-
- Steps that can run independently should have no shared depends_on
|
|
16
|
-
- depends_on contains the ids of steps that must finish before this one`),
|
|
28
|
+
- Output ONLY the SIMPLE literal OR the JSON array. No prose, no markdown, no code fences.
|
|
29
|
+
- Steps that can run in parallel: leave depends_on empty.
|
|
30
|
+
- Sequential dependencies: list step ids in depends_on.
|
|
31
|
+
- Be specific about file paths and command names — vague targets cause failures.`),
|
|
17
32
|
new HumanMessage(`Context:\n${context}\n\nUser request: ${userMessage}`)
|
|
18
33
|
]);
|
|
19
34
|
const content = response.content.trim();
|
|
@@ -5,10 +5,26 @@ import { join } from "path";
|
|
|
5
5
|
const PLAN_FILE = "plan.md";
|
|
6
6
|
const BLOCKED_EXTENSIONS = ['.ts', '.js', '.tsx', '.jsx', '.py', '.go', '.rs'];
|
|
7
7
|
export async function runPlanningAgent(userMessage, onUsage) {
|
|
8
|
-
const llm = makeLlm('
|
|
8
|
+
const llm = makeLlm('openai/gpt-oss-120b');
|
|
9
9
|
const planPath = join(process.cwd(), PLAN_FILE);
|
|
10
10
|
const existingPlan = existsSync(planPath) ? readFileSync(planPath, 'utf-8') : null;
|
|
11
|
-
const systemPrompt = existingPlan
|
|
11
|
+
const systemPrompt = existingPlan
|
|
12
|
+
? `You are a planning assistant. The user has an existing plan.md and wants to update it.
|
|
13
|
+
|
|
14
|
+
You receive: (1) the current plan, (2) the user's change request.
|
|
15
|
+
|
|
16
|
+
OUTPUT:
|
|
17
|
+
- The FULL updated plan in Markdown — this overwrites plan.md.
|
|
18
|
+
- After the plan, write exactly "---" on its own line.
|
|
19
|
+
- Then 1-3 bullets summarising what changed.
|
|
20
|
+
|
|
21
|
+
RULES:
|
|
22
|
+
- Preserve sections the user didn't ask to change.
|
|
23
|
+
- Apply requested changes precisely; don't expand scope.
|
|
24
|
+
- If the request is ambiguous, ask ONE clarifying question instead of guessing.
|
|
25
|
+
- Never delete existing sections unless the user explicitly says to.
|
|
26
|
+
- Avoid writing implementation code; this is planning, not execution.`
|
|
27
|
+
: `You are a planning assistant. Create a clear structured plan based on user's request.
|
|
12
28
|
|
|
13
29
|
BEFORE writing a plan:
|
|
14
30
|
- Look at the user's request and identify the key feature names, file paths, or concepts mentioned.
|
|
@@ -21,8 +37,7 @@ Rules:
|
|
|
21
37
|
- Use Markdown with clear sections and numbered steps
|
|
22
38
|
- Be specific: name files, components, decisions, trade-offs
|
|
23
39
|
- If the request is vague, prefer asking 1-2 clarifying questions over guessing
|
|
24
|
-
- After the plan, write exactly "---" on its own line, then 1-3 bullet points summarising what you created
|
|
25
|
-
`;
|
|
40
|
+
- After the plan, write exactly "---" on its own line, then 1-3 bullet points summarising what you created`;
|
|
26
41
|
const userContent = existingPlan ? `Current plan:\n\n${existingPlan}\n\nUser request:${userMessage}` : userMessage;
|
|
27
42
|
const response = await llm.invoke([
|
|
28
43
|
new SystemMessage(systemPrompt),
|
|
@@ -27,33 +27,29 @@ Workflow:
|
|
|
27
27
|
2. Run \`npx tsc --noEmit\` via the bash tool and capture type errors.
|
|
28
28
|
3. Run \`npx eslint <changed-files>\` via the bash tool (skip if eslint isn't configured).
|
|
29
29
|
4. Read the files. Combine static-analysis output with your own reading.
|
|
30
|
-
5. Return a
|
|
30
|
+
5. Return a plain-text report (terminal-friendly, NO markdown):
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
SUMMARY
|
|
33
33
|
<one or two sentences — overall health>
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
### CRITICAL
|
|
35
|
+
CRITICAL
|
|
38
36
|
- file.ts:LINE — what is wrong and why it matters
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
WARNING
|
|
41
39
|
- file.ts:LINE — what is wrong and how to think about it
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
SUGGESTION
|
|
44
42
|
- file.ts:LINE — small improvement
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
GOOD
|
|
47
45
|
- one or two things the code does well
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
- Each issue: filename:LINE — one sentence max.
|
|
56
|
-
- Total response: 25 lines maximum. Cut low-value suggestions if needed to stay under.`;
|
|
47
|
+
FORMAT RULES:
|
|
48
|
+
- No ##, ###, **bold**, *italic*. Plain text only.
|
|
49
|
+
- Each issue: one line, exactly "filename:LINE — single sentence".
|
|
50
|
+
- If a section has no items, write "none" under the label.
|
|
51
|
+
- Total response: 25 lines maximum. Cut low-value suggestions to stay under.
|
|
52
|
+
`;
|
|
57
53
|
export async function runReviewer(userMessage, onToolCall, model) {
|
|
58
54
|
const llm = makeLlm(model);
|
|
59
55
|
const tools = [listFilesTool, readFileTool, searchCodeTool, bashTool];
|
package/dist/llm/classifier.js
CHANGED
|
@@ -41,7 +41,14 @@ Tie-breakers:
|
|
|
41
41
|
- starts with the literal word "git" → git
|
|
42
42
|
- short greeting or opinion question → chat
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Confidence guide:
|
|
45
|
+
- "high": user's intent is unambiguous from this single message; pattern + verb both point one way.
|
|
46
|
+
- "low": message is short or uses pronouns ("this", "it", "that"), or could plausibly fit 2+ domains, or relies on context the history doesn't fully clarify.
|
|
47
|
+
|
|
48
|
+
Return JSON: {"domain":"<domain>","confidence":"high" or "low", "reason":"<one sentence>"}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
`),
|
|
45
52
|
new HumanMessage(history ? `Conversation so far:\n${history}\n\nNew message: ${prompt}` : `Message: ${prompt}`)
|
|
46
53
|
], { timeout: 3000 });
|
|
47
54
|
const raw = typeof response.content === 'string' ? response.content : '';
|
package/dist/llm/factory.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ChatOpenAI } from '@langchain/openai';
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
3
|
import { getAuthToken, getOrCreateAnonId } from '../auth.js';
|
|
4
|
-
|
|
4
|
+
function backendUrl() {
|
|
5
|
+
return process.env.GLITOOL_BACKEND ?? 'https://api.glit.in';
|
|
6
|
+
}
|
|
5
7
|
let currentRequestId = null;
|
|
6
8
|
export function startNewRequest() {
|
|
7
9
|
currentRequestId = randomUUID();
|
|
@@ -12,15 +14,16 @@ function requestIdHeader() {
|
|
|
12
14
|
}
|
|
13
15
|
export function makeLlm(model, extras = {}) {
|
|
14
16
|
if (process.env.OPENAI_API_KEY) {
|
|
15
|
-
return new ChatOpenAI({ model, apiKey: process.env.OPENAI_API_KEY, ...extras });
|
|
17
|
+
return new ChatOpenAI({ model, apiKey: process.env.OPENAI_API_KEY, streaming: true, ...extras });
|
|
16
18
|
}
|
|
17
19
|
const token = getAuthToken();
|
|
18
20
|
if (token) {
|
|
19
21
|
return new ChatOpenAI({
|
|
20
22
|
model,
|
|
21
23
|
apiKey: token,
|
|
24
|
+
streaming: true,
|
|
22
25
|
configuration: {
|
|
23
|
-
baseURL: `${
|
|
26
|
+
baseURL: `${backendUrl()}/v1`,
|
|
24
27
|
defaultHeaders: requestIdHeader(),
|
|
25
28
|
},
|
|
26
29
|
...extras,
|
|
@@ -29,8 +32,9 @@ export function makeLlm(model, extras = {}) {
|
|
|
29
32
|
return new ChatOpenAI({
|
|
30
33
|
model,
|
|
31
34
|
apiKey: 'anon',
|
|
35
|
+
streaming: true,
|
|
32
36
|
configuration: {
|
|
33
|
-
baseURL: `${
|
|
37
|
+
baseURL: `${backendUrl()}/v1`,
|
|
34
38
|
defaultHeaders: { 'X-Anon-ID': getOrCreateAnonId(), ...requestIdHeader() },
|
|
35
39
|
},
|
|
36
40
|
...extras,
|
|
@@ -46,7 +50,7 @@ export function makeInternalLlm(model, extras = {}) {
|
|
|
46
50
|
model,
|
|
47
51
|
apiKey: token ?? 'anon',
|
|
48
52
|
configuration: {
|
|
49
|
-
baseURL: `${
|
|
53
|
+
baseURL: `${backendUrl()}/v1`,
|
|
50
54
|
defaultHeaders: {
|
|
51
55
|
...anonHeaders,
|
|
52
56
|
'X-Glitool-Internal': 'true',
|
package/dist/memory.js
CHANGED
|
@@ -79,7 +79,14 @@ export async function generateAndSaveSummary(messages, llm) {
|
|
|
79
79
|
if (!readable.trim())
|
|
80
80
|
return;
|
|
81
81
|
const response = await llm.invoke([
|
|
82
|
-
new SystemMessage('
|
|
82
|
+
new SystemMessage(`You're writing a brief for the next coding session. The user will return to this project days or weeks later and need fast context recall.
|
|
83
|
+
|
|
84
|
+
In 2-3 sentences, summarize:
|
|
85
|
+
- What was built or changed (be specific: file paths, function names)
|
|
86
|
+
- Key decisions made (e.g., "chose JWT over sessions", "Postgres over Mongo")
|
|
87
|
+
- Likely next step
|
|
88
|
+
|
|
89
|
+
Avoid generic phrases like "we worked on the project". Cite specifics.`),
|
|
83
90
|
new HumanMessage(readable)
|
|
84
91
|
]);
|
|
85
92
|
const hash = crypto.createHash('md5').update(process.cwd()).digest('hex').slice(0, 8);
|
package/dist/monitor.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function emit(type, data = {}) {
|
|
2
|
+
if (!process.env.GLITOOL_DEV_MONITOR)
|
|
3
|
+
return;
|
|
4
|
+
fetch('http://localhost:4000/event', {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7
|
+
body: JSON.stringify({ type, ...data, t: new Date().toISOString() }),
|
|
8
|
+
}).catch(() => { });
|
|
9
|
+
}
|
package/dist/projectMemory.js
CHANGED
|
@@ -29,15 +29,30 @@ export async function extractAndSaveProjectMemory(messages, llm) {
|
|
|
29
29
|
return;
|
|
30
30
|
const existing = loadProjectMemory();
|
|
31
31
|
const response = await llm.invoke([
|
|
32
|
-
new SystemMessage(`Extract structured project facts from this conversation.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
new SystemMessage(`Extract structured project facts from this conversation.
|
|
33
|
+
|
|
34
|
+
OUTPUT REQUIREMENTS:
|
|
35
|
+
- Valid JSON ONLY. No markdown, no code fences, no prose.
|
|
36
|
+
- Exactly this shape:
|
|
37
|
+
{
|
|
38
|
+
"techStack": ["TypeScript", "Express", "MongoDB"],
|
|
39
|
+
"architectureDecisions": ["use JWT not sessions", "Mongo over Postgres for prototyping"],
|
|
40
|
+
"todos": ["add password reset", "wire Stripe webhooks"],
|
|
41
|
+
"lastUpdated": "${new Date().toISOString()}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
EXTRACTION RULES:
|
|
45
|
+
- techStack: only languages/frameworks/libraries that were USED or CHOSEN — not briefly mentioned and discarded.
|
|
46
|
+
- architectureDecisions: concrete choices ("use JWT for auth"), not vague phrases ("we'll handle auth").
|
|
47
|
+
- todos: actual next steps mentioned, not aspirational tangents.
|
|
48
|
+
|
|
49
|
+
${existing ? `MERGE WITH EXISTING:
|
|
50
|
+
- techStack: union (no duplicates).
|
|
51
|
+
- architectureDecisions: append new, keep old unless contradicted.
|
|
52
|
+
- todos: keep old unless explicitly completed in this conversation.
|
|
53
|
+
|
|
54
|
+
Existing memory:
|
|
55
|
+
${JSON.stringify(existing, null, 2)}` : ''}`),
|
|
41
56
|
new HumanMessage(readable)
|
|
42
57
|
]);
|
|
43
58
|
try {
|
package/dist/ui/App.js
CHANGED
|
@@ -336,7 +336,7 @@ export const App = ({ explainMode = false }) => {
|
|
|
336
336
|
"You've used your 5 free requests.",
|
|
337
337
|
'',
|
|
338
338
|
'Sign in with GitHub — free, 50 requests/month:',
|
|
339
|
-
'
|
|
339
|
+
' → https://glit.in/activate',
|
|
340
340
|
'',
|
|
341
341
|
'Type /signup to start the sign-in flow in your terminal.',
|
|
342
342
|
].join('\n'),
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -28,6 +28,16 @@ function formatCost(c) {
|
|
|
28
28
|
export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }) => {
|
|
29
29
|
const dotColor = STATE_COLOR[state];
|
|
30
30
|
const stateLabel = STATE_LABEL[state];
|
|
31
|
+
// Animate dot when working
|
|
32
|
+
const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
|
|
33
|
+
const [frame, setFrame] = React.useState(0);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (state !== 'working')
|
|
36
|
+
return;
|
|
37
|
+
const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), 150);
|
|
38
|
+
return () => clearInterval(id);
|
|
39
|
+
}, [state]);
|
|
40
|
+
const dotChar = state === 'working' ? SPINNER_FRAMES[frame] : symbols.statusDot;
|
|
31
41
|
const leftParts = [stateLabel];
|
|
32
42
|
if (detail)
|
|
33
43
|
leftParts.push(detail);
|
|
@@ -40,5 +50,5 @@ export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }
|
|
|
40
50
|
}
|
|
41
51
|
rightParts.push(model);
|
|
42
52
|
rightParts.push(formatTokens(tokens));
|
|
43
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children:
|
|
53
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: dotChar }), _jsxs(Text, { color: colors.ink2, children: [" ", leftParts.join(' . ')] })] }), _jsx(Box, { children: _jsx(Text, { color: colors.muted, children: rightParts.join(' · ') }) })] }));
|
|
44
54
|
};
|