@blockrun/franklin 3.3.2 → 3.5.0
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 +58 -7
- package/dist/agent/commands.d.ts +1 -1
- package/dist/agent/commands.js +128 -17
- package/dist/agent/compact.d.ts +2 -2
- package/dist/agent/compact.js +148 -22
- package/dist/agent/context.d.ts +8 -3
- package/dist/agent/context.js +301 -108
- package/dist/agent/error-classifier.d.ts +11 -2
- package/dist/agent/error-classifier.js +64 -10
- package/dist/agent/llm.d.ts +8 -1
- package/dist/agent/llm.js +114 -19
- package/dist/agent/loop.d.ts +1 -2
- package/dist/agent/loop.js +509 -61
- package/dist/agent/optimize.d.ts +2 -2
- package/dist/agent/optimize.js +9 -7
- package/dist/agent/permissions.d.ts +1 -1
- package/dist/agent/permissions.js +1 -1
- package/dist/agent/planner.d.ts +42 -0
- package/dist/agent/planner.js +110 -0
- package/dist/agent/reduce.d.ts +7 -1
- package/dist/agent/reduce.js +85 -3
- package/dist/agent/streaming-executor.d.ts +6 -1
- package/dist/agent/streaming-executor.js +83 -5
- package/dist/agent/tokens.d.ts +11 -2
- package/dist/agent/tokens.js +38 -5
- package/dist/agent/tool-guard.d.ts +27 -0
- package/dist/agent/tool-guard.js +324 -0
- package/dist/agent/types.d.ts +7 -1
- package/dist/agent/types.js +1 -1
- package/dist/banner.js +27 -40
- package/dist/brain/extract.d.ts +11 -0
- package/dist/brain/extract.js +154 -0
- package/dist/brain/index.d.ts +3 -0
- package/dist/brain/index.js +2 -0
- package/dist/brain/store.d.ts +42 -0
- package/dist/brain/store.js +225 -0
- package/dist/brain/types.d.ts +45 -0
- package/dist/brain/types.js +5 -0
- package/dist/commands/daemon.js +2 -1
- package/dist/commands/start.js +16 -3
- package/dist/config.js +1 -1
- package/dist/index.js +27 -2
- package/dist/learnings/extractor.d.ts +13 -0
- package/dist/learnings/extractor.js +69 -8
- package/dist/learnings/index.d.ts +1 -1
- package/dist/learnings/index.js +1 -1
- package/dist/learnings/store.js +42 -13
- package/dist/learnings/types.d.ts +1 -1
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/client.js +5 -5
- package/dist/mcp/config.d.ts +1 -1
- package/dist/mcp/config.js +1 -1
- package/dist/panel/html.d.ts +2 -0
- package/dist/panel/html.js +409 -146
- package/dist/panel/server.js +19 -0
- package/dist/pricing.js +3 -2
- package/dist/proxy/fallback.d.ts +3 -1
- package/dist/proxy/fallback.js +4 -4
- package/dist/proxy/server.js +29 -11
- package/dist/proxy/sse-translator.js +1 -1
- package/dist/router/categories.d.ts +21 -0
- package/dist/router/categories.js +96 -0
- package/dist/router/index.d.ts +9 -2
- package/dist/router/index.js +106 -27
- package/dist/router/local-elo.d.ts +32 -0
- package/dist/router/local-elo.js +107 -0
- package/dist/router/selector.d.ts +46 -0
- package/dist/router/selector.js +106 -0
- package/dist/session/storage.d.ts +5 -1
- package/dist/session/storage.js +24 -2
- package/dist/social/a11y.d.ts +1 -1
- package/dist/social/a11y.js +5 -1
- package/dist/social/browser.d.ts +5 -0
- package/dist/social/browser.js +22 -0
- package/dist/social/preflight.d.ts +4 -0
- package/dist/social/preflight.js +42 -3
- package/dist/stats/failures.d.ts +20 -0
- package/dist/stats/failures.js +63 -0
- package/dist/stats/format.d.ts +6 -0
- package/dist/stats/format.js +23 -0
- package/dist/stats/insights.js +1 -21
- package/dist/stats/session-tracker.d.ts +21 -0
- package/dist/stats/session-tracker.js +28 -0
- package/dist/stats/tracker.d.ts +1 -1
- package/dist/stats/tracker.js +1 -1
- package/dist/tools/bash.d.ts +14 -1
- package/dist/tools/bash.js +132 -7
- package/dist/tools/edit.js +77 -14
- package/dist/tools/glob.js +13 -3
- package/dist/tools/grep.js +30 -12
- package/dist/tools/imagegen.js +3 -3
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/read.d.ts +16 -2
- package/dist/tools/read.js +36 -8
- package/dist/tools/searchx.d.ts +6 -2
- package/dist/tools/searchx.js +221 -44
- package/dist/tools/subagent.js +37 -3
- package/dist/tools/task.js +43 -7
- package/dist/tools/validate.d.ts +11 -0
- package/dist/tools/validate.js +42 -0
- package/dist/tools/webfetch.js +18 -7
- package/dist/tools/websearch.js +41 -7
- package/dist/tools/write.js +26 -6
- package/dist/ui/app.js +31 -6
- package/dist/ui/model-picker.d.ts +1 -1
- package/dist/ui/model-picker.js +1 -1
- package/dist/ui/terminal.d.ts +1 -1
- package/dist/ui/terminal.js +1 -1
- package/package.json +2 -2
package/dist/agent/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Context Manager for
|
|
2
|
+
* Context Manager for Franklin
|
|
3
3
|
* Assembles system instructions, reads project config, injects environment info.
|
|
4
4
|
*/
|
|
5
5
|
import fs from 'node:fs';
|
|
@@ -7,50 +7,160 @@ import path from 'node:path';
|
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
import { loadLearnings, decayLearnings, saveLearnings, formatForPrompt } from '../learnings/store.js';
|
|
9
9
|
// ─── System Instructions Assembly ──────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Composable prompt sections — each independently maintainable and conditionally includable.
|
|
11
|
+
function getCoreInstructions() {
|
|
12
|
+
return `You are Franklin, an autonomous AI agent with a wallet. You help users with software engineering, marketing campaigns, trading signals, and any task that benefits from an agent that can reason, act, and spend.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
- Read before writing: always understand existing code before making changes.
|
|
15
|
-
- Be precise: make minimal, targeted changes. Don't refactor code you weren't asked to touch.
|
|
16
|
-
- Be safe: never introduce security vulnerabilities. Validate at system boundaries.
|
|
17
|
-
- Be honest: if you're unsure, say so. Don't guess at implementation details.
|
|
14
|
+
You are an interactive agent — not a chatbot. Use the tools available to you to accomplish tasks. Your job is to be a highly capable collaborator who takes initiative, makes progress, and delivers results.
|
|
18
15
|
|
|
19
|
-
#
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- **Bash**: Run shell commands. Default timeout 2min. Batch sequential commands with && to reduce round-trips.
|
|
24
|
-
- **Glob**: Find files by pattern. Skips node_modules/.git.
|
|
25
|
-
- **Grep**: Regex search. Default: file paths. output_mode "content" for matching lines.
|
|
26
|
-
- **WebFetch** / **WebSearch**: Fetch pages or search the web.
|
|
27
|
-
- **Task**: Track multi-step work.
|
|
28
|
-
- **Agent**: Spawn parallel sub-agents.
|
|
16
|
+
# System
|
|
17
|
+
- All text you output outside of tool use is displayed to the user. Use markdown for formatting.
|
|
18
|
+
- Tools are your hands. You MUST use tools to take action — do not describe what you would do without doing it. Never end your turn with a promise of future action — execute it now. Every response should either (a) contain tool calls that make progress, or (b) deliver a final result to the user.
|
|
19
|
+
- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make ALL independent tool calls in parallel. This is critical for performance. However, if tool calls depend on previous results, run them sequentially — do NOT use placeholders or guess dependent values.
|
|
29
20
|
|
|
30
|
-
#
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- Type
|
|
21
|
+
# Doing Tasks
|
|
22
|
+
- The user will primarily request software engineering tasks: solving bugs, adding features, refactoring, explaining code, and more. When given an unclear or generic instruction, consider it in the context of the current working directory and codebase.
|
|
23
|
+
- You are highly capable. Users come to you for ambitious tasks that would otherwise take too long. Defer to user judgment about scope.
|
|
24
|
+
- In general, do not propose changes to code you haven't read. Read it first. Understand existing code before suggesting modifications.
|
|
25
|
+
- Do not create files unless absolutely necessary. Prefer editing existing files to creating new ones.
|
|
26
|
+
- If an approach fails, diagnose why before switching tactics — read the error, check your assumptions, try a focused fix. Don't retry the identical action blindly, but don't abandon a viable approach after a single failure either. Escalate to the user only when genuinely stuck after investigation.
|
|
27
|
+
- For UI or frontend changes, always test in a browser before reporting the task as complete. Type checking and test suites verify code correctness, not feature correctness.
|
|
28
|
+
- Break down complex work with the Task tool to track progress. Mark each task completed as soon as you finish it — don't batch.
|
|
37
29
|
|
|
38
|
-
#
|
|
30
|
+
# Using Your Tools
|
|
31
|
+
- Do NOT use Bash when a dedicated tool exists. This is CRITICAL:
|
|
32
|
+
- Read files: use Read (NOT cat/head/tail/sed)
|
|
33
|
+
- Edit files: use Edit (NOT sed/awk)
|
|
34
|
+
- Create files: use Write (NOT echo/heredoc)
|
|
35
|
+
- Search content: use Grep (NOT grep/rg)
|
|
36
|
+
- Find files: use Glob (NOT find/ls)
|
|
37
|
+
- Reserve Bash exclusively for shell operations: builds, installs, git, npm/pip, processes, scripts.
|
|
38
|
+
- **Search strategy**: Glob/Grep for directed searches (known file/symbol). Use Agent for open-ended exploration that may require multiple rounds.
|
|
39
|
+
- **Batch bash**: chain sequential shell commands with && in a single call. Only split when you need intermediate output.
|
|
40
|
+
- **AskUser discipline**: Only use AskUser when you need explicit confirmation for a destructive action (deleting files, dropping databases). NEVER use AskUser to ask what the user wants — just answer their message directly. If the request is vague, make a reasonable assumption and proceed.
|
|
41
|
+
- Never write to /etc, /usr, ~/.ssh, ~/.aws. Don't commit secrets.`;
|
|
42
|
+
}
|
|
43
|
+
function getCodeStyleSection() {
|
|
44
|
+
return `# Code Quality
|
|
45
|
+
- Don't add features, refactor code, or make "improvements" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
|
|
46
|
+
- Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.
|
|
47
|
+
- Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is what the task actually requires — no speculative abstractions, but no half-finished implementations either. Three similar lines of code is better than a premature abstraction.
|
|
48
|
+
- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice insecure code, fix it immediately. Prioritize writing safe, secure, and correct code.
|
|
49
|
+
- Avoid backwards-compatibility hacks like renaming unused _vars, re-exporting types, adding // removed comments for removed code. If something is unused, delete it completely.
|
|
50
|
+
|
|
51
|
+
# Verification & Honesty
|
|
52
|
+
- Before reporting a task complete, verify it actually works: run the test, execute the script, check the output. If you can't verify, say so explicitly rather than claiming success.
|
|
53
|
+
- Report outcomes faithfully: if tests fail, say so with the relevant output. Never claim "all tests pass" when output shows failures. Never suppress or simplify failing checks to manufacture a green result. When a check did pass, state it plainly — do not hedge confirmed results with unnecessary disclaimers.`;
|
|
54
|
+
}
|
|
55
|
+
function getActionsSection() {
|
|
56
|
+
return `# Executing Actions with Care
|
|
57
|
+
Carefully consider the reversibility and blast radius of actions. You can freely take local, reversible actions like editing files or running tests. But for actions that are hard to reverse, affect shared systems, or could be destructive, check with the user before proceeding. The cost of pausing to confirm is low; the cost of an unwanted action (lost work, unintended messages, deleted branches) can be very high.
|
|
58
|
+
|
|
59
|
+
Examples of risky actions that warrant user confirmation:
|
|
60
|
+
- Destructive operations: deleting files/branches, dropping database tables, killing processes, rm -rf, overwriting uncommitted changes
|
|
61
|
+
- Hard-to-reverse operations: force-pushing, git reset --hard, amending published commits, removing or downgrading packages/dependencies
|
|
62
|
+
- Actions visible to others or that affect shared state: pushing code, creating/closing/commenting on PRs or issues, sending messages, posting to external services
|
|
63
|
+
- Uploading content to third-party web tools (pastebins, gists) publishes it — consider whether it could be sensitive
|
|
64
|
+
|
|
65
|
+
When you encounter an obstacle, do not use destructive actions as a shortcut. Identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting — it may represent the user's in-progress work.
|
|
66
|
+
|
|
67
|
+
A user approving an action once does NOT mean they approve it in all contexts. Match the scope of your actions to what was actually requested. When in doubt, ask before acting.`;
|
|
68
|
+
}
|
|
69
|
+
function getOutputEfficiencySection() {
|
|
70
|
+
return `# Output Efficiency
|
|
71
|
+
Go straight to the point. Lead with the action, not the reasoning. Do not restate what the user said. Do not narrate your actions ("Let me read the file...", "I'll now search for..."). Just call the tools.
|
|
72
|
+
|
|
73
|
+
Focus text output on:
|
|
74
|
+
- Decisions that need the user's input
|
|
75
|
+
- Results and conclusions (not the process)
|
|
76
|
+
- Errors or blockers that change the plan
|
|
77
|
+
|
|
78
|
+
If you can say it in one sentence, don't use three. Don't explain what tools you're going to use — the user can see tool calls directly. Only add text when it provides value beyond what the tool calls show.`;
|
|
79
|
+
}
|
|
80
|
+
function getToneAndStyleSection() {
|
|
81
|
+
return `# Tone and Style
|
|
82
|
+
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
|
|
83
|
+
- Your responses should be short and concise.
|
|
84
|
+
- When referencing specific functions or pieces of code include the pattern file_path:line_number to allow the user to easily navigate to the source code location.
|
|
85
|
+
- Do not use a colon before tool calls. Your tool calls may not be shown directly in the output, so text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
|
|
86
|
+
}
|
|
87
|
+
function getGitProtocolSection() {
|
|
88
|
+
return `# Git Protocol
|
|
89
|
+
Only create commits when the user explicitly asks. Do not commit proactively.
|
|
90
|
+
|
|
91
|
+
## Git Safety
|
|
92
|
+
- NEVER update the git config.
|
|
93
|
+
- NEVER run destructive git commands (push --force, reset --hard, checkout ., clean -f, branch -D) unless the user explicitly requests it.
|
|
94
|
+
- NEVER skip hooks (--no-verify) unless the user explicitly requests it.
|
|
95
|
+
- NEVER force push to main/master. Warn the user if they request it.
|
|
96
|
+
- ALWAYS create NEW commits rather than amending, unless the user explicitly requests a git amend. When a pre-commit hook fails, the commit did NOT happen — so --amend would modify the PREVIOUS commit. Fix the issue, re-stage, and create a NEW commit.
|
|
97
|
+
- When staging files, prefer adding specific files by name rather than using "git add -A" or "git add .", which can accidentally include sensitive files (.env, credentials) or large binaries.
|
|
98
|
+
|
|
99
|
+
## Commit Workflow
|
|
100
|
+
When the user asks you to commit:
|
|
101
|
+
1. Run git status and git diff to see all changes.
|
|
102
|
+
2. Run git log --oneline -5 to match the repo's commit message style.
|
|
103
|
+
3. Draft a concise commit message (1-2 sentences) that focuses on the "why" rather than the "what".
|
|
104
|
+
4. Stage relevant files by name. Do not commit files that likely contain secrets (.env, credentials.json).
|
|
105
|
+
5. Create the commit.
|
|
106
|
+
6. Run git status to verify success.
|
|
107
|
+
|
|
108
|
+
## PR Workflow
|
|
109
|
+
When the user asks you to create a PR:
|
|
110
|
+
1. Run git status, git diff, and git log to understand the full commit history for the branch.
|
|
111
|
+
2. Draft a short PR title (under 70 chars) and a description with Summary and Test Plan sections.
|
|
112
|
+
3. Push to remote with -u flag if needed.
|
|
113
|
+
4. Create the PR.`;
|
|
114
|
+
}
|
|
115
|
+
function getSocialMarketingSection() {
|
|
116
|
+
return `# X / Social Marketing — STRICT RULES
|
|
117
|
+
SearchX is the ONLY tool that can access X.com. WebSearch and WebFetch CANNOT access X.com content.
|
|
118
|
+
|
|
119
|
+
RULES (violations will produce garbage output):
|
|
120
|
+
1. Make ONE SearchX call per topic. Never retry with variations.
|
|
121
|
+
2. If SearchX returns empty, tell the user "No posts found" and suggest a different keyword. Do NOT fall back to WebSearch/WebFetch — they will return non-X content that you must NEVER present as X posts.
|
|
122
|
+
3. NEVER fabricate X post URLs. Every link you show MUST come from SearchX results. If a URL doesn't start with "https://x.com/", do NOT present it as an X post.
|
|
123
|
+
4. Present results as a numbered list. Each item: author, snippet, URL from SearchX, and a 1-2 sentence suggested reply.
|
|
124
|
+
5. Reply drafts must sound like a real human: short, specific to the post content, conversational. NO marketing speak, NO "Great point about...", NO corporate tone. Write like a smart friend, not a LinkedIn bot.
|
|
125
|
+
6. End with: "Reply to any? Give me the number."
|
|
126
|
+
7. Do NOT auto-post. Do NOT explain how the social system works.
|
|
127
|
+
|
|
128
|
+
When checking notifications/mentions: Use SearchX with mode="notifications". One call, done.`;
|
|
129
|
+
}
|
|
130
|
+
function getMissingAccessSection() {
|
|
131
|
+
return `# Missing Access
|
|
39
132
|
Always deliver results first using whatever tools work (WebSearch, WebFetch, etc.). Never let missing access block you.
|
|
40
133
|
After delivering results, if a better data source exists, add one line at the end:
|
|
41
134
|
"Tip: run franklin social setup && franklin social login x for live X data."
|
|
42
|
-
Do NOT check access before acting. Do NOT explain what you tried. Just deliver, then tip
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
135
|
+
Do NOT check access before acting. Do NOT explain what you tried. Just deliver, then tip.`;
|
|
136
|
+
}
|
|
137
|
+
function getToolPatternsSection() {
|
|
138
|
+
return `# Tool Selection Patterns
|
|
139
|
+
- **Finding files**: Glob first (by name/pattern), then Grep (by content), then Read (specific file). Don't start with Read unless you know the exact path.
|
|
140
|
+
- **Understanding code**: Glob for structure → Read key files → Grep for specific symbols/patterns. Don't read every file in a directory.
|
|
141
|
+
- **Making changes**: Read the file → Edit with targeted replacement → verify the edit worked (Read again or run tests). Never Edit without Reading first.
|
|
142
|
+
- **Running commands**: Use Bash for shell operations that have no dedicated tool. Chain commands with && when sequential. Use separate Bash calls when you need to inspect intermediate output.
|
|
143
|
+
- **Research**: WebSearch for discovery → WebFetch for specific URLs from search results. Don't WebFetch URLs you invented.
|
|
144
|
+
- **Complex tasks**: Use Agent to spawn sub-agents for 2+ independent research or implementation tasks. Don't do sequentially what can be done in parallel.
|
|
145
|
+
- **Multiple independent lookups**: Call all tools in a single response. NEVER make sequential calls when parallel calls would work.`;
|
|
146
|
+
}
|
|
147
|
+
function getTokenEfficiencySection() {
|
|
148
|
+
return `# Token Efficiency
|
|
149
|
+
- **Search once, not 10 times.** Do NOT run WebSearch with slight query variations. 3-5 searches MAX per topic. If results are empty, stop.
|
|
150
|
+
- **Stop after repeated misses.** If 2 similar searches return empty results, stop and synthesize what you have.
|
|
151
|
+
- **Read files once.** Do NOT re-read files you already read in this conversation. The content is already in your context. Check your memory before calling Read.
|
|
152
|
+
- **Present results early.** After 3 searches, present what you found. Do not keep searching — the user can ask for more.
|
|
153
|
+
- **Minimize tool calls.** Each tool call costs tokens. Before calling a tool, ask: do I already have this information? Can I answer from what's in context? If yes, don't call the tool.
|
|
154
|
+
- **Be concise.** Short, direct responses. Don't repeat what the user said. Don't explain what you're about to do — just do it. Don't narrate your tool calls.
|
|
155
|
+
- **Parallel, not sequential.** When you need 3 pieces of independent information, make 3 tool calls in ONE response — not 3 separate turns. Each turn has overhead.`;
|
|
156
|
+
}
|
|
157
|
+
function getVerificationSection() {
|
|
158
|
+
return `# Before Responding (verification checklist)
|
|
159
|
+
- Correctness: does your output satisfy the user's request?
|
|
160
|
+
- Grounding: are all factual claims backed by tool results, not your memory?
|
|
161
|
+
- URLs: does every link come from a tool result? NEVER fabricate URLs.
|
|
162
|
+
- Conciseness: is the response direct and actionable, not verbose filler?`;
|
|
163
|
+
}
|
|
54
164
|
// Cache assembled instructions per workingDir — avoids re-running git commands
|
|
55
165
|
// when sub-agents are spawned (common in parallel tool use patterns).
|
|
56
166
|
const _instructionCache = new Map();
|
|
@@ -58,11 +168,24 @@ const _instructionCache = new Map();
|
|
|
58
168
|
* Build the full system instructions array for a session.
|
|
59
169
|
* Result is memoized per workingDir for the process lifetime.
|
|
60
170
|
*/
|
|
61
|
-
export function assembleInstructions(workingDir) {
|
|
62
|
-
const
|
|
171
|
+
export function assembleInstructions(workingDir, model) {
|
|
172
|
+
const cacheKey = model ? `${workingDir}::${model}` : workingDir;
|
|
173
|
+
const cached = _instructionCache.get(cacheKey);
|
|
63
174
|
if (cached)
|
|
64
175
|
return cached;
|
|
65
|
-
const parts = [
|
|
176
|
+
const parts = [
|
|
177
|
+
getCoreInstructions(),
|
|
178
|
+
getCodeStyleSection(),
|
|
179
|
+
getActionsSection(),
|
|
180
|
+
getOutputEfficiencySection(),
|
|
181
|
+
getToneAndStyleSection(),
|
|
182
|
+
getGitProtocolSection(),
|
|
183
|
+
getSocialMarketingSection(),
|
|
184
|
+
getMissingAccessSection(),
|
|
185
|
+
getToolPatternsSection(),
|
|
186
|
+
getTokenEfficiencySection(),
|
|
187
|
+
getVerificationSection(),
|
|
188
|
+
];
|
|
66
189
|
// Read RUNCODE.md or CLAUDE.md from the project
|
|
67
190
|
const projectConfig = readProjectConfig(workingDir);
|
|
68
191
|
if (projectConfig) {
|
|
@@ -87,12 +210,71 @@ export function assembleInstructions(workingDir) {
|
|
|
87
210
|
}
|
|
88
211
|
}
|
|
89
212
|
catch { /* learnings are optional — never block startup */ }
|
|
90
|
-
|
|
213
|
+
// Model-specific execution guidance
|
|
214
|
+
if (model) {
|
|
215
|
+
parts.push(getModelGuidance(model));
|
|
216
|
+
}
|
|
217
|
+
_instructionCache.set(cacheKey, parts);
|
|
91
218
|
return parts;
|
|
92
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Model-family-specific execution guidance.
|
|
222
|
+
* Weak models get strict guardrails. Strong models get quality standards.
|
|
223
|
+
*/
|
|
224
|
+
export function getModelGuidance(model) {
|
|
225
|
+
const m = model.toLowerCase();
|
|
226
|
+
// Weak/cheap models: strict discipline to prevent looping and hallucination
|
|
227
|
+
if (m.includes('glm') || m.includes('gpt-oss') || m.includes('nemotron') ||
|
|
228
|
+
m.includes('minimax') || m.includes('devstral') || m.includes('llama-4')) {
|
|
229
|
+
return `# Execution Discipline (strict — this model requires guardrails)
|
|
230
|
+
- Make ONE tool call per task. Do NOT retry the same tool with query variations.
|
|
231
|
+
- If a tool returns empty results, tell the user immediately. Do NOT fall back to other tools.
|
|
232
|
+
- NEVER fabricate data, URLs, or quotes. If you don't have it, say so.
|
|
233
|
+
- Keep responses under 300 words. Be direct, not verbose.
|
|
234
|
+
- Before responding: does every URL and fact come from a tool result? If not, remove it.`;
|
|
235
|
+
}
|
|
236
|
+
// Medium models: balanced guidance
|
|
237
|
+
if (m.includes('kimi') || m.includes('grok') || m.includes('flash') ||
|
|
238
|
+
m.includes('haiku') || m.includes('deepseek') || m.includes('qwen')) {
|
|
239
|
+
return `# Execution Guidance
|
|
240
|
+
- Use tools to verify facts before stating them. Do not answer from memory when a tool can confirm.
|
|
241
|
+
- Batch independent tool calls in one response (parallel execution).
|
|
242
|
+
- If a tool fails, explain the failure to the user. Do not silently retry with a different tool.
|
|
243
|
+
- Before responding: are all claims grounded in tool output? Remove anything unverified.`;
|
|
244
|
+
}
|
|
245
|
+
// Strong models: quality standards + thinking guidance
|
|
246
|
+
if (m.includes('claude') || m.includes('gpt-5') || m.includes('opus') ||
|
|
247
|
+
m.includes('sonnet') || m.includes('gemini-2.5-pro') || m.includes('gemini-3') ||
|
|
248
|
+
m.includes('o3') || m.includes('o1') || m.includes('codex')) {
|
|
249
|
+
return `# Quality Standards (strong model)
|
|
250
|
+
- Keep calling tools until the task is complete AND the result is verified. Don't stop at "this should work" — prove it works.
|
|
251
|
+
- Before finalizing: check correctness, grounding in tool output, and formatting.
|
|
252
|
+
- If proceeding with incomplete information, label assumptions explicitly.
|
|
253
|
+
- Prefer depth over breadth — a thorough answer to one question beats shallow answers to many.
|
|
254
|
+
- Use your thinking to plan multi-step operations before executing them. Think about what tools you need, in what order, and what could go wrong.
|
|
255
|
+
- When debugging: think through the error systematically — read the error message, form a hypothesis, verify with tools, then fix. Don't guess-and-check.
|
|
256
|
+
- When making architectural decisions, consider second-order effects: will this change break other callers? Will it scale? Is it consistent with existing patterns?
|
|
257
|
+
- You have the capability to handle ambitious, complex tasks. Don't artificially constrain yourself — if the task needs 20 tool calls, make 20 tool calls.`;
|
|
258
|
+
}
|
|
259
|
+
// Default: basic guidance
|
|
260
|
+
return `# Execution Guidance
|
|
261
|
+
- Use tools to verify facts. Do not answer from memory when a tool can confirm.
|
|
262
|
+
- If a tool fails, tell the user. Do not silently retry.
|
|
263
|
+
- Before responding: are claims grounded in tool output?`;
|
|
264
|
+
}
|
|
93
265
|
/** Invalidate cache for a workingDir (call after /clear or session reset). */
|
|
94
266
|
export function invalidateInstructionCache(workingDir) {
|
|
95
|
-
|
|
267
|
+
if (workingDir) {
|
|
268
|
+
// Clear all entries for this workDir (any model)
|
|
269
|
+
for (const key of _instructionCache.keys()) {
|
|
270
|
+
if (key.startsWith(workingDir)) {
|
|
271
|
+
_instructionCache.delete(key);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
_instructionCache.clear();
|
|
277
|
+
}
|
|
96
278
|
}
|
|
97
279
|
// ─── Project Config ────────────────────────────────────────────────────────
|
|
98
280
|
/**
|
|
@@ -124,12 +306,26 @@ function readProjectConfig(dir) {
|
|
|
124
306
|
// ─── Environment ───────────────────────────────────────────────────────────
|
|
125
307
|
function buildEnvironmentSection(workingDir) {
|
|
126
308
|
const lines = ['# Environment'];
|
|
127
|
-
lines.push(`-
|
|
309
|
+
lines.push(`- Primary working directory: ${workingDir}`);
|
|
128
310
|
lines.push(`- Platform: ${process.platform}`);
|
|
129
311
|
lines.push(`- Node.js: ${process.version}`);
|
|
130
312
|
// Detect shell
|
|
131
313
|
const shell = process.env.SHELL || process.env.COMSPEC || 'unknown';
|
|
132
314
|
lines.push(`- Shell: ${path.basename(shell)}`);
|
|
315
|
+
// OS version
|
|
316
|
+
try {
|
|
317
|
+
const osRelease = execSync('uname -r', { encoding: 'utf-8', timeout: 2000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
318
|
+
lines.push(`- OS Version: ${process.platform === 'darwin' ? 'Darwin' : process.platform} ${osRelease}`);
|
|
319
|
+
}
|
|
320
|
+
catch { /* ignore */ }
|
|
321
|
+
// Git repo detection
|
|
322
|
+
try {
|
|
323
|
+
execSync('git rev-parse --is-inside-work-tree', { cwd: workingDir, timeout: 2000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
324
|
+
lines.push('- Is a git repository: true');
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
lines.push('- Is a git repository: false');
|
|
328
|
+
}
|
|
133
329
|
// Date
|
|
134
330
|
lines.push(`- Date: ${new Date().toISOString().split('T')[0]}`);
|
|
135
331
|
return lines.join('\n');
|
|
@@ -139,76 +335,73 @@ const GIT_TIMEOUT_MS = 5_000;
|
|
|
139
335
|
// Max chars for git log output — long commit messages can bloat the system prompt
|
|
140
336
|
const MAX_GIT_LOG_CHARS = 2_000;
|
|
141
337
|
function getGitContext(workingDir) {
|
|
338
|
+
const gitCmd = (cmd) => execSync(cmd, {
|
|
339
|
+
cwd: workingDir,
|
|
340
|
+
encoding: 'utf-8',
|
|
341
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
342
|
+
timeout: GIT_TIMEOUT_MS,
|
|
343
|
+
}).trim();
|
|
142
344
|
try {
|
|
143
|
-
|
|
144
|
-
cwd: workingDir,
|
|
145
|
-
encoding: 'utf-8',
|
|
146
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
147
|
-
timeout: GIT_TIMEOUT_MS,
|
|
148
|
-
}).trim();
|
|
149
|
-
if (isGit !== 'true')
|
|
345
|
+
if (gitCmd('git rev-parse --is-inside-work-tree') !== 'true')
|
|
150
346
|
return null;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const lines = [];
|
|
352
|
+
// Current branch
|
|
353
|
+
try {
|
|
354
|
+
const branch = gitCmd('git branch --show-current');
|
|
355
|
+
if (branch)
|
|
356
|
+
lines.push(`Current branch: ${branch}`);
|
|
357
|
+
}
|
|
358
|
+
catch { /* detached HEAD */ }
|
|
359
|
+
// Main/default branch detection (for PR context)
|
|
360
|
+
try {
|
|
361
|
+
// Check common default branch names
|
|
362
|
+
const refs = gitCmd('git branch -l main master develop 2>/dev/null');
|
|
363
|
+
const mainBranch = refs.split('\n')
|
|
364
|
+
.map(l => l.trim().replace('* ', ''))
|
|
365
|
+
.find(b => ['main', 'master'].includes(b));
|
|
366
|
+
if (mainBranch)
|
|
367
|
+
lines.push(`Main branch: ${mainBranch}`);
|
|
368
|
+
}
|
|
369
|
+
catch { /* ignore */ }
|
|
370
|
+
// Git status with file paths (not just counts)
|
|
371
|
+
try {
|
|
372
|
+
const status = gitCmd('git status --short');
|
|
373
|
+
if (status) {
|
|
374
|
+
const statusLines = status.split('\n');
|
|
375
|
+
// Cap at 20 files to avoid bloating the prompt
|
|
376
|
+
const cap = 20;
|
|
377
|
+
const display = statusLines.slice(0, cap).join('\n');
|
|
378
|
+
lines.push(`\nStatus:\n${display}`);
|
|
379
|
+
if (statusLines.length > cap) {
|
|
380
|
+
lines.push(`... and ${statusLines.length - cap} more files`);
|
|
178
381
|
}
|
|
179
382
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
let log = execSync('git log --oneline -5', {
|
|
184
|
-
cwd: workingDir,
|
|
185
|
-
encoding: 'utf-8',
|
|
186
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
187
|
-
timeout: GIT_TIMEOUT_MS,
|
|
188
|
-
}).trim();
|
|
189
|
-
if (log) {
|
|
190
|
-
if (log.length > MAX_GIT_LOG_CHARS) {
|
|
191
|
-
log = log.slice(0, MAX_GIT_LOG_CHARS) + '\n... (truncated)';
|
|
192
|
-
}
|
|
193
|
-
lines.push(`\nRecent commits:\n${log}`);
|
|
194
|
-
}
|
|
383
|
+
else {
|
|
384
|
+
lines.push('Status: clean');
|
|
195
385
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
lines.push(`User: ${user}`);
|
|
386
|
+
}
|
|
387
|
+
catch { /* ignore */ }
|
|
388
|
+
// Recent commits
|
|
389
|
+
try {
|
|
390
|
+
let log = gitCmd('git log --oneline -5');
|
|
391
|
+
if (log) {
|
|
392
|
+
if (log.length > MAX_GIT_LOG_CHARS) {
|
|
393
|
+
log = log.slice(0, MAX_GIT_LOG_CHARS) + '\n... (truncated)';
|
|
394
|
+
}
|
|
395
|
+
lines.push(`\nRecent commits:\n${log}`);
|
|
207
396
|
}
|
|
208
|
-
catch { /* ignore */ }
|
|
209
|
-
return lines.length > 0 ? lines.join('\n') : null;
|
|
210
397
|
}
|
|
211
|
-
catch {
|
|
212
|
-
|
|
398
|
+
catch { /* ignore */ }
|
|
399
|
+
// Git user
|
|
400
|
+
try {
|
|
401
|
+
const user = gitCmd('git config user.name');
|
|
402
|
+
if (user)
|
|
403
|
+
lines.push(`\nGit user: ${user}`);
|
|
213
404
|
}
|
|
405
|
+
catch { /* ignore */ }
|
|
406
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
214
407
|
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Classify model/runtime errors so recovery and UX can be more consistent.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Claude Code's multi-layer error classification:
|
|
5
|
+
* - Separate 'overloaded' category (529) from general server errors — shorter retry budget
|
|
6
|
+
* - Auth errors (401) get special handling (token refresh, not retry)
|
|
7
|
+
* - EPIPE/connection reset handled as network errors (retryable)
|
|
3
8
|
*/
|
|
4
|
-
export type AgentErrorCategory = 'rate_limit' | 'payment' | 'network' | 'timeout' | 'context_limit' | 'server' | 'unknown';
|
|
9
|
+
export type AgentErrorCategory = 'rate_limit' | 'payment' | 'network' | 'timeout' | 'context_limit' | 'overloaded' | 'server' | 'auth' | 'unknown';
|
|
5
10
|
export interface AgentErrorInfo {
|
|
6
11
|
category: AgentErrorCategory;
|
|
7
|
-
label: 'RateLimit' | 'Payment' | 'Network' | 'Timeout' | 'Context' | 'Server' | 'Unknown';
|
|
12
|
+
label: 'RateLimit' | 'Payment' | 'Network' | 'Timeout' | 'Context' | 'Overloaded' | 'Server' | 'Auth' | 'Unknown';
|
|
8
13
|
isTransient: boolean;
|
|
14
|
+
/** Max retries for this error type (overrides default). undefined = use default. */
|
|
15
|
+
maxRetries?: number;
|
|
16
|
+
/** User-facing suggestion for how to recover. Appended to error message in UI. */
|
|
17
|
+
suggestion?: string;
|
|
9
18
|
}
|
|
10
19
|
export declare function classifyAgentError(message: string): AgentErrorInfo;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Classify model/runtime errors so recovery and UX can be more consistent.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Claude Code's multi-layer error classification:
|
|
5
|
+
* - Separate 'overloaded' category (529) from general server errors — shorter retry budget
|
|
6
|
+
* - Auth errors (401) get special handling (token refresh, not retry)
|
|
7
|
+
* - EPIPE/connection reset handled as network errors (retryable)
|
|
3
8
|
*/
|
|
4
9
|
function includesAny(text, patterns) {
|
|
5
10
|
return patterns.some((p) => text.includes(p));
|
|
@@ -14,37 +19,86 @@ export function classifyAgentError(message) {
|
|
|
14
19
|
'402',
|
|
15
20
|
'free tier',
|
|
16
21
|
])) {
|
|
17
|
-
return {
|
|
22
|
+
return {
|
|
23
|
+
category: 'payment', label: 'Payment', isTransient: false,
|
|
24
|
+
suggestion: 'Run `franklin balance` to check funds. Try /model free for free models.',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Auth errors — not retryable (need user action: re-login, new API key)
|
|
28
|
+
if (includesAny(err, [
|
|
29
|
+
'401',
|
|
30
|
+
'unauthorized',
|
|
31
|
+
'invalid api key',
|
|
32
|
+
'invalid x-api-key',
|
|
33
|
+
'authentication failed',
|
|
34
|
+
])) {
|
|
35
|
+
return {
|
|
36
|
+
category: 'auth', label: 'Auth', isTransient: false,
|
|
37
|
+
suggestion: 'Check your API key or wallet configuration. Run `franklin setup` to reconfigure.',
|
|
38
|
+
};
|
|
18
39
|
}
|
|
19
40
|
if (includesAny(err, [
|
|
20
41
|
'429',
|
|
21
42
|
'rate limit',
|
|
22
43
|
'too many requests',
|
|
23
44
|
])) {
|
|
24
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
category: 'rate_limit', label: 'RateLimit', isTransient: true,
|
|
47
|
+
suggestion: 'Try /model to switch to a different model, or wait a moment and /retry.',
|
|
48
|
+
};
|
|
25
49
|
}
|
|
26
50
|
if (includesAny(err, [
|
|
27
51
|
'prompt is too long',
|
|
28
52
|
'context length',
|
|
29
53
|
'maximum context',
|
|
54
|
+
'prompt too long',
|
|
55
|
+
'token limit exceeded',
|
|
30
56
|
])) {
|
|
31
|
-
return {
|
|
57
|
+
return {
|
|
58
|
+
category: 'context_limit', label: 'Context', isTransient: false,
|
|
59
|
+
suggestion: 'Run /compact to compress conversation history.',
|
|
60
|
+
};
|
|
32
61
|
}
|
|
33
62
|
if (includesAny(err, [
|
|
34
63
|
'timeout',
|
|
35
64
|
'timed out',
|
|
65
|
+
'deadline exceeded',
|
|
36
66
|
])) {
|
|
37
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
category: 'timeout', label: 'Timeout', isTransient: true,
|
|
69
|
+
suggestion: 'Check your network connection. Use /retry to try again.',
|
|
70
|
+
};
|
|
38
71
|
}
|
|
39
72
|
if (includesAny(err, [
|
|
40
73
|
'fetch failed',
|
|
41
74
|
'econnrefused',
|
|
42
75
|
'econnreset',
|
|
43
76
|
'enotfound',
|
|
77
|
+
'epipe',
|
|
44
78
|
'network',
|
|
45
79
|
'socket hang up',
|
|
80
|
+
'connection reset',
|
|
81
|
+
'dns resolution',
|
|
82
|
+
])) {
|
|
83
|
+
return {
|
|
84
|
+
category: 'network', label: 'Network', isTransient: true,
|
|
85
|
+
suggestion: 'Check your network connection. Use /retry to try again.',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// 529 / Overloaded — separate from generic server errors
|
|
89
|
+
// Claude Code only allows 3 retries for these (they tend to persist)
|
|
90
|
+
if (includesAny(err, [
|
|
91
|
+
'529',
|
|
92
|
+
'overloaded',
|
|
93
|
+
'workers are busy',
|
|
94
|
+
'all workers are busy',
|
|
95
|
+
'server busy',
|
|
96
|
+
'capacity',
|
|
46
97
|
])) {
|
|
47
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
category: 'overloaded', label: 'Overloaded', isTransient: true, maxRetries: 3,
|
|
100
|
+
suggestion: 'The model is overloaded. Try /model to switch, or wait and /retry.',
|
|
101
|
+
};
|
|
48
102
|
}
|
|
49
103
|
if (includesAny(err, [
|
|
50
104
|
'500',
|
|
@@ -54,15 +108,15 @@ export function classifyAgentError(message) {
|
|
|
54
108
|
'internal server error',
|
|
55
109
|
'bad gateway',
|
|
56
110
|
'service unavailable',
|
|
57
|
-
'temporarily unavailable',
|
|
58
|
-
'workers are busy', // "All workers are busy"
|
|
59
|
-
'server busy',
|
|
60
|
-
'overloaded',
|
|
111
|
+
'temporarily unavailable',
|
|
61
112
|
'please retry later',
|
|
62
113
|
'retry in a few',
|
|
63
114
|
'upstream error',
|
|
64
115
|
])) {
|
|
65
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
category: 'server', label: 'Server', isTransient: true,
|
|
118
|
+
suggestion: 'Server error. Use /retry to try again, or /model to switch models.',
|
|
119
|
+
};
|
|
66
120
|
}
|
|
67
121
|
return { category: 'unknown', label: 'Unknown', isTransient: false };
|
|
68
122
|
}
|