@strayl/agent 0.1.2 → 0.1.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 +6 -7
- package/package.json +5 -1
- package/skills/api-creation/SKILL.md +631 -0
- package/skills/authentication/SKILL.md +294 -0
- package/skills/frontend-design/SKILL.md +108 -0
- package/skills/landing-creation/SKILL.md +125 -0
- package/skills/reference/SKILL.md +149 -0
- package/skills/web-application-creation/SKILL.md +231 -0
- package/src/agent.ts +0 -465
- package/src/checkpoints/manager.ts +0 -112
- package/src/context/manager.ts +0 -185
- package/src/context/summarizer.ts +0 -104
- package/src/context/trim.ts +0 -55
- package/src/emitter.ts +0 -14
- package/src/hitl/manager.ts +0 -77
- package/src/hitl/transport.ts +0 -13
- package/src/index.ts +0 -116
- package/src/llm/client.ts +0 -276
- package/src/llm/gemini-native.ts +0 -307
- package/src/llm/models.ts +0 -64
- package/src/middleware/compose.ts +0 -24
- package/src/middleware/credential-scrubbing.ts +0 -31
- package/src/middleware/forbidden-packages.ts +0 -107
- package/src/middleware/plan-mode.ts +0 -143
- package/src/middleware/prompt-caching.ts +0 -21
- package/src/middleware/tool-compression.ts +0 -25
- package/src/middleware/tool-filter.ts +0 -13
- package/src/prompts/implementation-mode.md +0 -16
- package/src/prompts/plan-mode.md +0 -51
- package/src/prompts/system.ts +0 -173
- package/src/skills/loader.ts +0 -53
- package/src/stdin-listener.ts +0 -61
- package/src/subagents/definitions.ts +0 -72
- package/src/subagents/manager.ts +0 -161
- package/src/todos/manager.ts +0 -61
- package/src/tools/builtin/delete.ts +0 -29
- package/src/tools/builtin/edit.ts +0 -74
- package/src/tools/builtin/exec.ts +0 -216
- package/src/tools/builtin/glob.ts +0 -104
- package/src/tools/builtin/grep.ts +0 -115
- package/src/tools/builtin/ls.ts +0 -54
- package/src/tools/builtin/move.ts +0 -31
- package/src/tools/builtin/read.ts +0 -69
- package/src/tools/builtin/write.ts +0 -42
- package/src/tools/executor.ts +0 -51
- package/src/tools/external/database.ts +0 -285
- package/src/tools/external/enter-plan-mode.ts +0 -34
- package/src/tools/external/generate-image.ts +0 -110
- package/src/tools/external/hitl-tools.ts +0 -118
- package/src/tools/external/preview.ts +0 -28
- package/src/tools/external/proxy-fetch.ts +0 -51
- package/src/tools/external/task.ts +0 -38
- package/src/tools/external/wait.ts +0 -20
- package/src/tools/external/web-fetch.ts +0 -57
- package/src/tools/external/web-search.ts +0 -61
- package/src/tools/registry.ts +0 -36
- package/src/tools/zod-to-json-schema.ts +0 -86
- package/src/types.ts +0 -151
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { Middleware, ToolCall } from "../types.js";
|
|
2
|
-
|
|
3
|
-
const CREDENTIAL_PATTERNS = [
|
|
4
|
-
/(?:token|api_key|apikey|secret|password|credential|auth|bearer)\s*[:=]\s*["']?([^\s"',}{]{8,})["']?/gi,
|
|
5
|
-
/(?:sk|pk|rk|ak)-[a-zA-Z0-9]{20,}/g, // Common API key prefixes
|
|
6
|
-
/ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens
|
|
7
|
-
/gho_[a-zA-Z0-9]{36}/g,
|
|
8
|
-
/github_pat_[a-zA-Z0-9_]{82}/g,
|
|
9
|
-
/xox[bpas]-[a-zA-Z0-9-]+/g, // Slack tokens
|
|
10
|
-
/eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}/g, // JWT-like tokens
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
function scrubCredentials(text: string): string {
|
|
14
|
-
let scrubbed = text;
|
|
15
|
-
for (const pattern of CREDENTIAL_PATTERNS) {
|
|
16
|
-
scrubbed = scrubbed.replace(pattern, (match) => {
|
|
17
|
-
// Keep first 4 chars for identification
|
|
18
|
-
const prefix = match.slice(0, 4);
|
|
19
|
-
return `${prefix}****[REDACTED]`;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
return scrubbed;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const credentialScrubbingMiddleware: Middleware = {
|
|
26
|
-
name: "credential-scrubbing",
|
|
27
|
-
wrapToolCall: async (_call: ToolCall, next: () => Promise<string>): Promise<string> => {
|
|
28
|
-
const result = await next();
|
|
29
|
-
return scrubCredentials(result);
|
|
30
|
-
},
|
|
31
|
-
};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { Middleware, ToolCall } from "../types.js";
|
|
2
|
-
|
|
3
|
-
const FORBIDDEN_PACKAGES: Record<string, string> = {
|
|
4
|
-
// Database drivers
|
|
5
|
-
pg: "Use @neondatabase/serverless instead",
|
|
6
|
-
mysql2: "Use @planetscale/database instead",
|
|
7
|
-
"better-sqlite3": "Use @libsql/client instead",
|
|
8
|
-
sqlite3: "Use @libsql/client instead",
|
|
9
|
-
mongodb: "Use mongoose with serverless adapter instead",
|
|
10
|
-
|
|
11
|
-
// ORMs
|
|
12
|
-
prisma: "Use drizzle-orm instead",
|
|
13
|
-
"@prisma/client": "Use drizzle-orm instead",
|
|
14
|
-
typeorm: "Use drizzle-orm instead",
|
|
15
|
-
sequelize: "Use drizzle-orm instead",
|
|
16
|
-
knex: "Use drizzle-orm instead",
|
|
17
|
-
"mikro-orm": "Use drizzle-orm instead",
|
|
18
|
-
|
|
19
|
-
// Server frameworks (not needed with TanStack Start)
|
|
20
|
-
express: "Not needed — use TanStack Start + Nitro",
|
|
21
|
-
fastify: "Not needed — use TanStack Start + Nitro",
|
|
22
|
-
koa: "Not needed — use TanStack Start + Nitro",
|
|
23
|
-
hapi: "Not needed — use TanStack Start + Nitro",
|
|
24
|
-
"nest.js": "Not needed — use TanStack Start + Nitro",
|
|
25
|
-
"@nestjs/core": "Not needed — use TanStack Start + Nitro",
|
|
26
|
-
|
|
27
|
-
// Native bindings
|
|
28
|
-
bcrypt: "Use bcryptjs instead (pure JS)",
|
|
29
|
-
sharp: "Use @cf-wasm/photon instead",
|
|
30
|
-
canvas: "Use @napi-rs/canvas instead",
|
|
31
|
-
|
|
32
|
-
// Build tools (don't change the build setup)
|
|
33
|
-
webpack: "Project uses Vite — don't add webpack",
|
|
34
|
-
parcel: "Project uses Vite — don't add parcel",
|
|
35
|
-
rollup: "Project uses Vite via TanStack Start",
|
|
36
|
-
|
|
37
|
-
// Outdated frameworks
|
|
38
|
-
gatsby: "Use Next.js, Remix, or TanStack Start instead",
|
|
39
|
-
"create-react-app": "Use Vite + React instead",
|
|
40
|
-
|
|
41
|
-
// Auth
|
|
42
|
-
passport: "Use better-auth instead",
|
|
43
|
-
"next-auth": "Use better-auth instead",
|
|
44
|
-
|
|
45
|
-
// CSS
|
|
46
|
-
"styled-components": "Use Tailwind CSS instead",
|
|
47
|
-
emotion: "Use Tailwind CSS instead",
|
|
48
|
-
"@emotion/react": "Use Tailwind CSS instead",
|
|
49
|
-
sass: "Use Tailwind CSS instead",
|
|
50
|
-
less: "Use Tailwind CSS instead",
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
function extractNpmPackages(command: string): string[] {
|
|
54
|
-
// Split chained commands
|
|
55
|
-
const parts = command.split(/&&|;|\|/).map(s => s.trim());
|
|
56
|
-
const packages: string[] = [];
|
|
57
|
-
|
|
58
|
-
for (const part of parts) {
|
|
59
|
-
// Match npm install/add patterns
|
|
60
|
-
const match = part.match(/(?:npm\s+(?:install|i|add)|yarn\s+add|pnpm\s+(?:add|install)|bun\s+(?:add|install))\s+(.+)/i);
|
|
61
|
-
if (!match) continue;
|
|
62
|
-
|
|
63
|
-
const args = match[1].split(/\s+/);
|
|
64
|
-
for (const arg of args) {
|
|
65
|
-
if (arg.startsWith("-")) continue; // Skip flags
|
|
66
|
-
// Strip version specifier: @scope/pkg@1.2.3 → @scope/pkg
|
|
67
|
-
const pkg = arg.replace(/@[^@/]+$/, "");
|
|
68
|
-
if (pkg) packages.push(pkg);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return packages;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const forbiddenPackagesMiddleware: Middleware = {
|
|
76
|
-
name: "forbidden-packages",
|
|
77
|
-
wrapToolCall: async (call: ToolCall, next: () => Promise<string>): Promise<string> => {
|
|
78
|
-
if (call.function.name !== "exec") return next();
|
|
79
|
-
|
|
80
|
-
let args: { command?: string };
|
|
81
|
-
try {
|
|
82
|
-
args = JSON.parse(call.function.arguments);
|
|
83
|
-
} catch {
|
|
84
|
-
return next();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!args.command) return next();
|
|
88
|
-
|
|
89
|
-
const packages = extractNpmPackages(args.command);
|
|
90
|
-
const blocked: Array<{ pkg: string; reason: string }> = [];
|
|
91
|
-
|
|
92
|
-
for (const pkg of packages) {
|
|
93
|
-
if (FORBIDDEN_PACKAGES[pkg]) {
|
|
94
|
-
blocked.push({ pkg, reason: FORBIDDEN_PACKAGES[pkg] });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (blocked.length > 0) {
|
|
99
|
-
const details = blocked.map(b => `- ${b.pkg}: ${b.reason}`).join("\n");
|
|
100
|
-
return JSON.stringify({
|
|
101
|
-
error: `Blocked packages detected:\n${details}\n\nUse the suggested alternatives instead.`,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return next();
|
|
106
|
-
},
|
|
107
|
-
};
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import type { Middleware, ToolCall, AgentMode, Message } from "../types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tools blocked in plan mode — anything that writes, executes, or modifies.
|
|
5
|
-
*/
|
|
6
|
-
const PLAN_MODE_BLOCKED = new Set([
|
|
7
|
-
"exec",
|
|
8
|
-
"getLogs",
|
|
9
|
-
"write_file",
|
|
10
|
-
"edit_file",
|
|
11
|
-
"delete_file",
|
|
12
|
-
"move_file",
|
|
13
|
-
"show_preview",
|
|
14
|
-
"generate_image",
|
|
15
|
-
"requestEnvVar",
|
|
16
|
-
"enterPlanMode",
|
|
17
|
-
"create_database",
|
|
18
|
-
"run_database_query",
|
|
19
|
-
"prepare_database_migration",
|
|
20
|
-
"complete_database_migration",
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Tools blocked in implementation/normal mode (planning-only tools).
|
|
25
|
-
*/
|
|
26
|
-
const IMPL_MODE_BLOCKED = new Set([
|
|
27
|
-
"writePlan",
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Creates middleware that filters tools based on current agent mode.
|
|
32
|
-
*/
|
|
33
|
-
export function createPlanModeMiddleware(getMode: () => AgentMode): Middleware {
|
|
34
|
-
return {
|
|
35
|
-
name: "planModeFilter",
|
|
36
|
-
filterTools: (tools) => {
|
|
37
|
-
const mode = getMode();
|
|
38
|
-
if (mode === "plan") {
|
|
39
|
-
return tools.filter(t => !PLAN_MODE_BLOCKED.has(t.function.name));
|
|
40
|
-
}
|
|
41
|
-
if (mode === "implement") {
|
|
42
|
-
// Block planning tools + enterPlanMode
|
|
43
|
-
return tools.filter(t =>
|
|
44
|
-
!IMPL_MODE_BLOCKED.has(t.function.name) && t.function.name !== "enterPlanMode"
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
// Normal mode: block planning-only tools
|
|
48
|
-
return tools.filter(t => !IMPL_MODE_BLOCKED.has(t.function.name));
|
|
49
|
-
},
|
|
50
|
-
wrapToolCall: async (call: ToolCall, next: () => Promise<string>) => {
|
|
51
|
-
const mode = getMode();
|
|
52
|
-
const name = call.function.name;
|
|
53
|
-
|
|
54
|
-
if (mode === "plan" && PLAN_MODE_BLOCKED.has(name)) {
|
|
55
|
-
return JSON.stringify({
|
|
56
|
-
error: `Tool "${name}" is not available in Plan Mode. Use read-only tools only.`,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (mode !== "plan" && IMPL_MODE_BLOCKED.has(name)) {
|
|
60
|
-
return JSON.stringify({
|
|
61
|
-
error: `Tool "${name}" is only available in Plan Mode.`,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (mode === "implement" && name === "enterPlanMode") {
|
|
65
|
-
return JSON.stringify({
|
|
66
|
-
error: `Cannot enter plan mode during implementation. The plan is already confirmed.`,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return next();
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Performs context reset when transitioning from plan → implement.
|
|
77
|
-
*
|
|
78
|
-
* Finds the last writePlan tool call, extracts the plan content,
|
|
79
|
-
* removes all planning messages, and injects the plan as a system message.
|
|
80
|
-
*
|
|
81
|
-
* Returns the confirmed plan content (for saving to disk), or null if not found.
|
|
82
|
-
*/
|
|
83
|
-
export function resetContextForImplementation(messages: Message[]): {
|
|
84
|
-
newMessages: Message[];
|
|
85
|
-
planContent: string | null;
|
|
86
|
-
} {
|
|
87
|
-
// Find the last writePlan tool call in assistant messages
|
|
88
|
-
let writePlanIdx = -1;
|
|
89
|
-
let planContent = "";
|
|
90
|
-
let planTitle = "";
|
|
91
|
-
|
|
92
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
93
|
-
const msg = messages[i];
|
|
94
|
-
if (msg.role !== "assistant" || !msg.tool_calls) continue;
|
|
95
|
-
|
|
96
|
-
const writePlanCall = msg.tool_calls.find(tc => tc.function.name === "writePlan");
|
|
97
|
-
if (writePlanCall) {
|
|
98
|
-
writePlanIdx = i;
|
|
99
|
-
try {
|
|
100
|
-
const args = JSON.parse(writePlanCall.function.arguments);
|
|
101
|
-
planContent = args.content ?? "";
|
|
102
|
-
planTitle = args.title ?? "";
|
|
103
|
-
} catch {
|
|
104
|
-
// Malformed args
|
|
105
|
-
}
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (writePlanIdx === -1 || !planContent) {
|
|
111
|
-
return { newMessages: messages, planContent: null };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Find the matching tool result message to skip it too
|
|
115
|
-
const writePlanCall = messages[writePlanIdx].tool_calls!.find(tc => tc.function.name === "writePlan")!;
|
|
116
|
-
let skipUntilIdx = writePlanIdx + 1;
|
|
117
|
-
for (let j = writePlanIdx + 1; j < messages.length; j++) {
|
|
118
|
-
if (messages[j].role === "tool" && messages[j].tool_call_id === writePlanCall.id) {
|
|
119
|
-
skipUntilIdx = j + 1;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Keep system messages and any messages after the writePlan call+result
|
|
125
|
-
const systemMsgs = messages.filter(m => m.role === "system");
|
|
126
|
-
const preservedMsgs = messages.slice(skipUntilIdx);
|
|
127
|
-
|
|
128
|
-
// Inject confirmed plan as system message
|
|
129
|
-
const planSystemMsg: Message = {
|
|
130
|
-
role: "user",
|
|
131
|
-
content: `[Plan Context Reset — IMPLEMENTATION MODE]\n\nThe user has reviewed and confirmed the following plan. Do NOT repeat, summarize, or rewrite this plan. Proceed directly to implementing it step by step.\n\n## Confirmed Plan: ${planTitle}\n\n${planContent}\n\n---\nIMPORTANT: The plan above is confirmed. Start implementing immediately. Do NOT output the plan again.`,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const ackMsg: Message = {
|
|
135
|
-
role: "assistant",
|
|
136
|
-
content: "Plan confirmed. I'll start implementing it now.",
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
newMessages: [...systemMsgs, planSystemMsg, ackMsg, ...preservedMsgs],
|
|
141
|
-
planContent,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Middleware, Message } from "../types.js";
|
|
2
|
-
|
|
3
|
-
export function createPromptCachingMiddleware(modelName: string): Middleware {
|
|
4
|
-
const isAnthropic = modelName.includes("anthropic/") || modelName.includes("claude");
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
name: "prompt-caching",
|
|
8
|
-
beforeModel: (messages: Message[]): Message[] => {
|
|
9
|
-
if (!isAnthropic) return messages;
|
|
10
|
-
|
|
11
|
-
return messages.map((msg, i) => {
|
|
12
|
-
if (msg.role !== "system") return msg;
|
|
13
|
-
// Inject cache_control for Anthropic system messages
|
|
14
|
-
return {
|
|
15
|
-
...msg,
|
|
16
|
-
cache_control: { type: "ephemeral" },
|
|
17
|
-
} as Message & { cache_control: { type: string } };
|
|
18
|
-
});
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Middleware, Message } from "../types.js";
|
|
2
|
-
|
|
3
|
-
// Claude Code approach: fully clear old tool outputs, keep recent ones intact.
|
|
4
|
-
// Tool call messages (assistant side) stay visible so the agent remembers
|
|
5
|
-
// *what* it did, but old raw outputs are replaced with a short placeholder.
|
|
6
|
-
// Reference: https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
|
|
7
|
-
|
|
8
|
-
const KEEP_RECENT = 8;
|
|
9
|
-
|
|
10
|
-
export const toolCompressionMiddleware: Middleware = {
|
|
11
|
-
name: "tool-compression",
|
|
12
|
-
beforeModel: (messages: Message[]): Message[] => {
|
|
13
|
-
const boundary = Math.max(0, messages.length - KEEP_RECENT);
|
|
14
|
-
|
|
15
|
-
return messages.map((msg, i) => {
|
|
16
|
-
if (i >= boundary) return msg;
|
|
17
|
-
if (msg.role !== "tool") return msg;
|
|
18
|
-
if (typeof msg.content !== "string") return msg;
|
|
19
|
-
|
|
20
|
-
// Fully mask old tool outputs — agent remembers the call but not the raw output
|
|
21
|
-
const name = msg.name ?? "tool";
|
|
22
|
-
return { ...msg, content: `[${name} output cleared]` };
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from "../types.js";
|
|
2
|
-
import type OpenAI from "openai";
|
|
3
|
-
|
|
4
|
-
export function createToolFilterMiddleware(blockedTools: string[]): Middleware {
|
|
5
|
-
const blocked = new Set(blockedTools);
|
|
6
|
-
return {
|
|
7
|
-
name: "tool-filter",
|
|
8
|
-
filterTools: (tools: OpenAI.ChatCompletionTool[]): OpenAI.ChatCompletionTool[] => {
|
|
9
|
-
if (blocked.size === 0) return tools;
|
|
10
|
-
return tools.filter(t => t.type === "function" && !blocked.has(t.function.name));
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Implementation Mode
|
|
2
|
-
|
|
3
|
-
You are in IMPLEMENTATION MODE. A plan has been confirmed — implement it now.
|
|
4
|
-
|
|
5
|
-
## Mandatory First Actions
|
|
6
|
-
1. Review the confirmed plan (it's in the conversation context)
|
|
7
|
-
2. Create todos from the plan steps using write_todos
|
|
8
|
-
3. Execute each step in order
|
|
9
|
-
|
|
10
|
-
## Rules
|
|
11
|
-
- Do NOT re-plan or second-guess the plan
|
|
12
|
-
- Do NOT loop on errors more than twice — if something fails twice, move on and note the issue
|
|
13
|
-
- Trust the plan — it was reviewed and confirmed by the user
|
|
14
|
-
- Focus on: creating files, installing packages, writing code, running commands
|
|
15
|
-
- Update todo status as you work (in_progress → completed)
|
|
16
|
-
- Clear all todos when done: write_todos({ todos: [] })
|
package/src/prompts/plan-mode.md
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Plan Mode
|
|
2
|
-
|
|
3
|
-
You are in PLAN MODE. Your job is to create a detailed implementation plan before any code is written.
|
|
4
|
-
|
|
5
|
-
## Allowed Actions
|
|
6
|
-
- Read files (read_file, ls, grep, glob)
|
|
7
|
-
- Search the web (web_search, web_fetch)
|
|
8
|
-
- Delegate research to sub-agents (task)
|
|
9
|
-
- Interview the user (interviewUser)
|
|
10
|
-
- Submit a plan (writePlan)
|
|
11
|
-
|
|
12
|
-
## Blocked Actions
|
|
13
|
-
You CANNOT write, edit, execute, or delete files in this mode. Focus entirely on research and planning.
|
|
14
|
-
|
|
15
|
-
## Workflow
|
|
16
|
-
1. **Read relevant skills** — Check /skills/ for any matching skill files
|
|
17
|
-
2. **Explore the codebase** — Understand existing code, patterns, and conventions
|
|
18
|
-
3. **Research unknowns** — Use web search for unfamiliar APIs, libraries, or patterns
|
|
19
|
-
4. **Interview the user** (optional) — Ask clarifying questions if requirements are unclear
|
|
20
|
-
5. **Submit the plan** — Use writePlan with a structured plan
|
|
21
|
-
|
|
22
|
-
## Plan Format
|
|
23
|
-
Your plan MUST include:
|
|
24
|
-
|
|
25
|
-
### Overview
|
|
26
|
-
Brief description of what will be built and why.
|
|
27
|
-
|
|
28
|
-
### Dependencies
|
|
29
|
-
New packages to install (with justification).
|
|
30
|
-
|
|
31
|
-
### Database Schema (if applicable)
|
|
32
|
-
Table definitions, relations, indexes.
|
|
33
|
-
|
|
34
|
-
### Step-by-Step Implementation
|
|
35
|
-
Numbered steps with:
|
|
36
|
-
- Exact file paths to create/modify
|
|
37
|
-
- What each file should contain (describe the logic, not pseudocode)
|
|
38
|
-
- Dependencies between steps
|
|
39
|
-
|
|
40
|
-
### UI/UX (if applicable)
|
|
41
|
-
Component hierarchy, user flows, responsive behavior.
|
|
42
|
-
|
|
43
|
-
### Verification
|
|
44
|
-
How to test that the implementation works.
|
|
45
|
-
|
|
46
|
-
## Quality Guidelines
|
|
47
|
-
- 8-20 steps for full features
|
|
48
|
-
- Be SPECIFIC — "Create src/routes/settings.tsx with a form for updating user preferences" not "Create settings page"
|
|
49
|
-
- Reference existing patterns in the codebase
|
|
50
|
-
- Consider edge cases, error handling, and loading states
|
|
51
|
-
- Note any decisions that need user confirmation
|
package/src/prompts/system.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { buildSkillsPrompt, type Skill } from "../skills/loader.js";
|
|
2
|
-
import type { AgentMode } from "../types.js";
|
|
3
|
-
|
|
4
|
-
export function buildSystemPrompt(config: {
|
|
5
|
-
workDir: string;
|
|
6
|
-
skills: Skill[];
|
|
7
|
-
systemPromptExtra?: string;
|
|
8
|
-
mode?: AgentMode;
|
|
9
|
-
}): string {
|
|
10
|
-
const parts = [AGENT_INSTRUCTIONS];
|
|
11
|
-
|
|
12
|
-
const skillsPrompt = buildSkillsPrompt(config.skills);
|
|
13
|
-
if (skillsPrompt) parts.push(skillsPrompt);
|
|
14
|
-
|
|
15
|
-
// Append mode-specific prompt
|
|
16
|
-
const mode = config.mode ?? "normal";
|
|
17
|
-
if (mode === "plan") parts.push(PLAN_MODE_SYSTEM_PROMPT);
|
|
18
|
-
if (mode === "implement") parts.push(IMPLEMENTATION_MODE_PROMPT);
|
|
19
|
-
|
|
20
|
-
if (config.systemPromptExtra) parts.push(config.systemPromptExtra);
|
|
21
|
-
|
|
22
|
-
return parts.join("\n\n");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const AGENT_INSTRUCTIONS = `You are Strayl, an expert software engineer agent. You help users build, debug, and improve web applications.
|
|
26
|
-
|
|
27
|
-
## Working Directory
|
|
28
|
-
- ALWAYS use relative paths (never absolute paths like /home/daytona/...)
|
|
29
|
-
- The project root is your current working directory
|
|
30
|
-
- All file operations are relative to the project root
|
|
31
|
-
|
|
32
|
-
## Hard Rules
|
|
33
|
-
- Do NOT run \`npm create\`, \`npx create-*\`, or any scaffolding commands
|
|
34
|
-
- Do NOT create .env files — environment variables are managed by the platform
|
|
35
|
-
- Do NOT run \`npm audit fix\` — it breaks dependencies
|
|
36
|
-
- Do NOT change build tool configuration unless explicitly asked
|
|
37
|
-
- Do NOT add ESLint, Prettier, or other linting tools unless asked
|
|
38
|
-
- Do NOT import Node.js built-in modules (node:fs, node:child_process, node:net, node:os) in application code
|
|
39
|
-
- Do NOT use eval() or new Function() — blocked by the sandbox
|
|
40
|
-
- Forbidden packages are blocked automatically — use suggested alternatives
|
|
41
|
-
- Always use --yes or -y flags for non-interactive CLI commands
|
|
42
|
-
|
|
43
|
-
## Stack Reference
|
|
44
|
-
- **React 19.x**: use(), useActionState(), ref as prop (not forwardRef)
|
|
45
|
-
- **TanStack Start 1.x**: createServerFn({ method: 'POST' }).inputValidator((d: unknown) => schema.parse(d)).handler(...)
|
|
46
|
-
- **TanStack Router 1.x**: createFileRoute(), $param, __root.tsx
|
|
47
|
-
- **Vite 7.x**: tanstackStart() plugin in vite.config.ts (NOT app.config.ts)
|
|
48
|
-
- **Tailwind CSS 4.x**: Config in src/styles.css via @theme {} — NO tailwind.config.js
|
|
49
|
-
- **shadcn/ui**: Latest, OKLCH colors, @custom-variant dark
|
|
50
|
-
|
|
51
|
-
## Before Writing Code
|
|
52
|
-
1. Understand the project: read package.json, src/routes/__root.tsx
|
|
53
|
-
2. Check what exists: glob src/components/**, check package.json
|
|
54
|
-
3. Read the relevant skill before implementing (if skills are available)
|
|
55
|
-
4. NEVER delegate skill reading to sub-agents — they can't access /skills/
|
|
56
|
-
|
|
57
|
-
## Protected Files
|
|
58
|
-
Use edit_file (NOT write_file) for: package.json, tsconfig.json, vite.config.ts, src/styles.css, src/routes/__root.tsx, src/lib/i18n.ts
|
|
59
|
-
|
|
60
|
-
## File Operations
|
|
61
|
-
- 3+ edits to the same file → use write_file with the complete content instead
|
|
62
|
-
- write_file is ATOMIC — don't re-read after writing, don't delete/recreate
|
|
63
|
-
- Shell commands: always use --yes or -y flags
|
|
64
|
-
|
|
65
|
-
## Background Commands
|
|
66
|
-
When running long commands, use background: true and wait:
|
|
67
|
-
- npm install → wait 20-30s
|
|
68
|
-
- npx shadcn add → wait 20-40s
|
|
69
|
-
- npm run build → wait 15-45s
|
|
70
|
-
- npm run dev → wait 5-10s, then show_preview
|
|
71
|
-
|
|
72
|
-
## Sub-agents
|
|
73
|
-
Use sub-agents aggressively for parallel speedup:
|
|
74
|
-
- **code-explorer**: Before modifying unfamiliar code, explore it first
|
|
75
|
-
- **web-researcher**: Before installing new packages, research compatibility
|
|
76
|
-
- **general-purpose**: Combined exploration + research
|
|
77
|
-
- When debugging: spawn BOTH code-explorer AND web-researcher in PARALLEL
|
|
78
|
-
- Always parallelize — spawn multiple sub-agents simultaneously when possible
|
|
79
|
-
|
|
80
|
-
## Todos
|
|
81
|
-
Create todos when: 3+ files involved, installing packages + writing code, sequential dependencies.
|
|
82
|
-
Do NOT create todos for: single file edits, research only, single commands.
|
|
83
|
-
Granularity: ONE completable action per todo.
|
|
84
|
-
Lifecycle: create todos → set in_progress → completed → write_todos({ todos: [] }) when all done.`;
|
|
85
|
-
|
|
86
|
-
const PLAN_MODE_SYSTEM_PROMPT = `
|
|
87
|
-
|
|
88
|
-
## PLAN MODE — YOU CAN ONLY DO 3 THINGS
|
|
89
|
-
|
|
90
|
-
You are in PLAN MODE. You have exactly 3 jobs:
|
|
91
|
-
1. **Research** — read and explore the codebase AND read relevant skills to understand how to build
|
|
92
|
-
2. **Interview** — ask the user clarifying questions if requirements are ambiguous
|
|
93
|
-
3. **Write the plan** — call \`writePlan\` to submit a structured plan for user approval
|
|
94
|
-
|
|
95
|
-
That's it. You CANNOT implement anything. You CANNOT scaffold projects, install packages, create files, run commands, or make any changes. You are READ-ONLY.
|
|
96
|
-
|
|
97
|
-
### CRITICAL: Plans go in writePlan, NOT in chat text
|
|
98
|
-
Do NOT write the plan as a text message. The plan MUST be submitted via the \`writePlan\` tool so the user can review and confirm it in the UI. If you output a plan as text, the user cannot confirm or reject it.
|
|
99
|
-
|
|
100
|
-
### ALLOWED TOOLS
|
|
101
|
-
- \`read_file\`, \`ls\`, \`grep\`, \`glob\` — explore existing code
|
|
102
|
-
- \`task\` — delegate to code-explorer or web-researcher sub-agents for broad searches
|
|
103
|
-
- \`askUser\` — ask clarifying questions (only if genuinely ambiguous, 2-4 max)
|
|
104
|
-
- \`writePlan\` — submit the final plan (THE ONLY WAY to deliver your plan)
|
|
105
|
-
- \`web_search\`, \`web_fetch\` — research docs and APIs
|
|
106
|
-
|
|
107
|
-
Everything else is BLOCKED and will return an error. Do NOT retry failed tool calls.
|
|
108
|
-
|
|
109
|
-
### WORKFLOW
|
|
110
|
-
1. **Read relevant skills FIRST** — before anything else, identify which skills apply and read their SKILL.md files. These contain mandatory steps, package choices, and patterns you MUST follow. **IMPORTANT: Read skill files YOURSELF using read_file — NEVER delegate skill reading to subagents.**
|
|
111
|
-
2. **Explore existing codebase** — read package.json, route structure, existing components. Delegate to code-explorer for broad scans.
|
|
112
|
-
3. **Research unknowns** — use task(web-researcher) for APIs, libraries, or patterns you're unsure about.
|
|
113
|
-
4. **Interview (optional)** — only if genuinely ambiguous requirements (2-4 questions max).
|
|
114
|
-
5. **writePlan** — submit the detailed plan.
|
|
115
|
-
|
|
116
|
-
Keep chat messages short — brief status updates only ("Reading skills...", "Exploring the codebase...", "Writing the plan."). The plan content goes in writePlan, not in chat.
|
|
117
|
-
|
|
118
|
-
### PLAN FORMAT (inside writePlan) — THIS IS CRITICAL
|
|
119
|
-
|
|
120
|
-
Your plan will be executed by the agent AFTER the user confirms it. The agent following the plan may be using a weaker model. Therefore, the plan must be **extremely detailed and specific** — like a recipe that anyone can follow.
|
|
121
|
-
|
|
122
|
-
**Structure:**
|
|
123
|
-
1. **Overview** (2-3 sentences) — what we're building, key tech choices
|
|
124
|
-
2. **Dependencies** — exact packages to install with versions if critical
|
|
125
|
-
3. **Database Schema** — full table definitions with columns, types, relations, indices
|
|
126
|
-
4. **Step-by-step Implementation** — each step must include:
|
|
127
|
-
- Exact file paths to create or modify
|
|
128
|
-
- What the file should contain (describe the logic, exports, key code patterns)
|
|
129
|
-
- Which skill instructions to follow for that step
|
|
130
|
-
- Dependencies on previous steps
|
|
131
|
-
5. **UI/UX Details** — component hierarchy, layout description, key interactions
|
|
132
|
-
6. **Verification** — how to confirm each major milestone works
|
|
133
|
-
|
|
134
|
-
**Quality standards:**
|
|
135
|
-
- Each step should be ONE focused action (create a file, install a package, configure something)
|
|
136
|
-
- Reference specific files: "Create \`src/db/schema.ts\` with tables: users, tasks, projects..."
|
|
137
|
-
- Reference skill instructions: "Follow the authentication skill for Better Auth setup"
|
|
138
|
-
- Include key decisions: WHY this approach over alternatives
|
|
139
|
-
- 8-20 steps is normal for a full feature. Do NOT artificially compress into 3-5 vague steps
|
|
140
|
-
|
|
141
|
-
### HARD RULES
|
|
142
|
-
- You CANNOT exit plan mode. Only the user can confirm/reject.
|
|
143
|
-
- Do NOT implement anything — no scaffolding, no npm install, no file creation.
|
|
144
|
-
- Do NOT loop — if a tool errors, move on.
|
|
145
|
-
- ALWAYS read relevant skill files before writing the plan.
|
|
146
|
-
- Do NOT write shallow plans. Take the time to research thoroughly and write detailed steps.`;
|
|
147
|
-
|
|
148
|
-
const IMPLEMENTATION_MODE_PROMPT = `
|
|
149
|
-
|
|
150
|
-
## IMPLEMENTATION MODE — PLAN CONFIRMED
|
|
151
|
-
|
|
152
|
-
A plan has been confirmed by the user and is ready for execution. Your ONLY job now is to implement this plan.
|
|
153
|
-
|
|
154
|
-
### YOUR FIRST ACTIONS (MANDATORY)
|
|
155
|
-
1. The confirmed plan content is included in context above — review it now
|
|
156
|
-
2. Create todos from the plan steps using \`write_todos\`
|
|
157
|
-
3. Begin executing the plan step by step
|
|
158
|
-
|
|
159
|
-
### HARD RULES
|
|
160
|
-
- Do NOT write a plan as text in chat — the plan already exists as a confirmed file
|
|
161
|
-
- Do NOT call \`enterPlanMode\` — you are past the planning phase
|
|
162
|
-
- Do NOT call \`askUser\` or \`writePlan\` — planning is done
|
|
163
|
-
- Do NOT re-research or re-plan — trust the confirmed plan and execute it
|
|
164
|
-
- If you encounter an issue during implementation, fix it and continue — do NOT restart planning
|
|
165
|
-
- Focus on implementation: create files, install packages, write code, run commands
|
|
166
|
-
|
|
167
|
-
### WORKFLOW
|
|
168
|
-
1. Review the confirmed plan in context above
|
|
169
|
-
2. Create todos from plan steps
|
|
170
|
-
3. For each step: mark todo in_progress → execute → mark completed
|
|
171
|
-
4. After all steps: verify the build works, show preview if applicable`;
|
|
172
|
-
|
|
173
|
-
export { PLAN_MODE_SYSTEM_PROMPT, IMPLEMENTATION_MODE_PROMPT };
|
package/src/skills/loader.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export interface Skill {
|
|
5
|
-
name: string;
|
|
6
|
-
description: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function loadSkills(skillsDir: string): Promise<Skill[]> {
|
|
10
|
-
const skills: Skill[] = [];
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
14
|
-
for (const entry of entries) {
|
|
15
|
-
if (!entry.isDirectory()) continue;
|
|
16
|
-
|
|
17
|
-
const skillMdPath = path.join(skillsDir, entry.name, "SKILL.md");
|
|
18
|
-
try {
|
|
19
|
-
const content = await fs.readFile(skillMdPath, "utf-8");
|
|
20
|
-
// Extract description from first paragraph or heading
|
|
21
|
-
const lines = content.split("\n").filter(l => l.trim());
|
|
22
|
-
const description = lines[0]?.replace(/^#+\s*/, "") ?? entry.name;
|
|
23
|
-
skills.push({ name: entry.name, description });
|
|
24
|
-
} catch {
|
|
25
|
-
// No SKILL.md — skip
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
} catch {
|
|
29
|
-
// Skills dir doesn't exist — that's fine
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return skills;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function buildSkillsPrompt(skills: Skill[]): string {
|
|
36
|
-
if (skills.length === 0) return "";
|
|
37
|
-
|
|
38
|
-
const lines = [
|
|
39
|
-
"\n## Skills",
|
|
40
|
-
"You have access to the following skills. Before implementing a feature that matches a skill, ALWAYS read the skill file first.",
|
|
41
|
-
"",
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
for (const skill of skills) {
|
|
45
|
-
lines.push(`- **${skill.name}**: ${skill.description}`);
|
|
46
|
-
lines.push(` Read: \`/skills/${skill.name}/SKILL.md\``);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
lines.push("");
|
|
50
|
-
lines.push("Skills contain mandatory workflows, not optional suggestions. Follow them exactly.");
|
|
51
|
-
|
|
52
|
-
return lines.join("\n");
|
|
53
|
-
}
|