pragma-so 0.1.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/cli/index.ts +882 -0
- package/index.ts +3 -0
- package/package.json +53 -0
- package/server/connectorBinaries.ts +103 -0
- package/server/connectorRegistry.ts +158 -0
- package/server/conversation/adapterRegistry.ts +53 -0
- package/server/conversation/adapters/claudeAdapter.ts +138 -0
- package/server/conversation/adapters/codexAdapter.ts +142 -0
- package/server/conversation/adapters.ts +224 -0
- package/server/conversation/executeRunner.ts +1191 -0
- package/server/conversation/gitWorkflow.ts +1037 -0
- package/server/conversation/models.ts +23 -0
- package/server/conversation/pragmaCli.ts +34 -0
- package/server/conversation/prompts.ts +335 -0
- package/server/conversation/store.ts +805 -0
- package/server/conversation/titleGenerator.ts +106 -0
- package/server/conversation/turnRunner.ts +365 -0
- package/server/conversation/types.ts +134 -0
- package/server/db.ts +837 -0
- package/server/http/middleware.ts +31 -0
- package/server/http/schemas.ts +430 -0
- package/server/http/validators.ts +38 -0
- package/server/index.ts +6560 -0
- package/server/process/runCommand.ts +142 -0
- package/server/stores/agentStore.ts +167 -0
- package/server/stores/connectorStore.ts +299 -0
- package/server/stores/humanStore.ts +28 -0
- package/server/stores/skillStore.ts +127 -0
- package/server/stores/taskStore.ts +371 -0
- package/shared/net.ts +24 -0
- package/tsconfig.json +14 -0
- package/ui/index.html +14 -0
- package/ui/public/favicon-32.png +0 -0
- package/ui/public/favicon.png +0 -0
- package/ui/src/App.jsx +1338 -0
- package/ui/src/api.js +954 -0
- package/ui/src/components/CodeView.jsx +319 -0
- package/ui/src/components/ConnectionsView.jsx +1004 -0
- package/ui/src/components/ContextView.jsx +315 -0
- package/ui/src/components/ConversationDrawer.jsx +963 -0
- package/ui/src/components/EmptyPane.jsx +20 -0
- package/ui/src/components/FeedView.jsx +773 -0
- package/ui/src/components/FilesView.jsx +257 -0
- package/ui/src/components/InlineChatView.jsx +158 -0
- package/ui/src/components/InputBar.jsx +476 -0
- package/ui/src/components/OnboardingModal.jsx +112 -0
- package/ui/src/components/OutputPanel.jsx +658 -0
- package/ui/src/components/PlanProposalPanel.jsx +177 -0
- package/ui/src/components/RightPanel.jsx +951 -0
- package/ui/src/components/SettingsView.jsx +186 -0
- package/ui/src/components/Sidebar.jsx +247 -0
- package/ui/src/components/TestingPane.jsx +198 -0
- package/ui/src/components/testing/ApiTesterPanel.jsx +187 -0
- package/ui/src/components/testing/LogViewerPanel.jsx +64 -0
- package/ui/src/components/testing/TerminalPanel.jsx +104 -0
- package/ui/src/components/testing/WebPreviewPanel.jsx +78 -0
- package/ui/src/hooks/useAgents.js +81 -0
- package/ui/src/hooks/useConversation.js +252 -0
- package/ui/src/hooks/useTasks.js +161 -0
- package/ui/src/hooks/useWorkspace.js +259 -0
- package/ui/src/lib/agentIcon.js +10 -0
- package/ui/src/lib/conversationUtils.js +575 -0
- package/ui/src/main.jsx +10 -0
- package/ui/src/styles.css +6899 -0
- package/ui/vite.config.mjs +6 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PragmaError } from "../db";
|
|
2
|
+
import { getAdapterDefinition, allAdapterDefinitions } from "./adapterRegistry";
|
|
3
|
+
|
|
4
|
+
export function resolveModelId(harness: string, label: string): string {
|
|
5
|
+
const def = getAdapterDefinition(harness);
|
|
6
|
+
const modelId = def.models[label];
|
|
7
|
+
if (!modelId) {
|
|
8
|
+
throw new PragmaError(
|
|
9
|
+
"INVALID_MODEL",
|
|
10
|
+
400,
|
|
11
|
+
`Unknown model for harness ${harness}: ${label}`,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return modelId;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function modelOptionsByHarness(): Record<string, string[]> {
|
|
18
|
+
const result: Record<string, string[]> = {};
|
|
19
|
+
for (const def of allAdapterDefinitions()) {
|
|
20
|
+
result[def.id] = Object.keys(def.models);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function resolvePragmaCliCommand(runtimeDir: string): string {
|
|
5
|
+
const fromEnv = normalizeCliCommand(process.env.PRAGMA_CLI_COMMAND);
|
|
6
|
+
if (fromEnv) {
|
|
7
|
+
return fromEnv;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const candidates = [
|
|
11
|
+
join(runtimeDir, "..", "cli", "index.js"),
|
|
12
|
+
join(runtimeDir, "..", "..", "dist", "cli", "index.js"),
|
|
13
|
+
join(process.cwd(), "dist", "cli", "index.js"),
|
|
14
|
+
].map((candidate) => resolve(candidate));
|
|
15
|
+
|
|
16
|
+
const existing = candidates.find((candidate) => existsSync(candidate));
|
|
17
|
+
if (existing) {
|
|
18
|
+
return `node ${quoteShellArg(existing)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return "pragma";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeCliCommand(value: string | undefined): string | null {
|
|
25
|
+
if (typeof value !== "string") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function quoteShellArg(value: string): string {
|
|
33
|
+
return `"${value.replace(/["\\$`]/g, "\\$&")}"`;
|
|
34
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { ReasoningEffort } from "./types";
|
|
3
|
+
|
|
4
|
+
type WorkerCandidate = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string | null;
|
|
8
|
+
harness: string;
|
|
9
|
+
modelLabel: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function buildPrompt(
|
|
13
|
+
mode: "chat" | "plan" | "execute",
|
|
14
|
+
message: string,
|
|
15
|
+
reasoningEffort: ReasoningEffort = "medium",
|
|
16
|
+
pragmaCliCommand = "pragma",
|
|
17
|
+
options: {
|
|
18
|
+
planCandidates?: WorkerCandidate[];
|
|
19
|
+
workspaceIsEmpty?: boolean;
|
|
20
|
+
workspaceDir?: string;
|
|
21
|
+
codeRepos?: string[];
|
|
22
|
+
conversationHistory?: Array<{ role: string; content: string }>;
|
|
23
|
+
} = {},
|
|
24
|
+
): string {
|
|
25
|
+
const cleanMessage = message.trim();
|
|
26
|
+
const reasoningLine = formatReasoningInstruction(reasoningEffort);
|
|
27
|
+
const cli = pragmaCliCommand.trim() || "pragma";
|
|
28
|
+
|
|
29
|
+
if (mode === "chat") {
|
|
30
|
+
const chatParts = [
|
|
31
|
+
"You are a pragmatic software engineering assistant.",
|
|
32
|
+
"Chat mode is read-only. You may read files, search code, and run read-only shell commands, but you must not create, edit, or delete any files.",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const wsDir = options.workspaceDir?.trim();
|
|
36
|
+
if (wsDir) {
|
|
37
|
+
chatParts.push(`Your working directory is \`${wsDir}\`. Stay within this directory.`);
|
|
38
|
+
const repos = options.codeRepos;
|
|
39
|
+
if (repos && repos.length > 0) {
|
|
40
|
+
chatParts.push(`Repos under code/: ${repos.join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const dbQueryCommand = `${cli} db-query --sql "<SELECT statement>"`;
|
|
45
|
+
chatParts.push(
|
|
46
|
+
"To inspect workspace database state (tasks, threads, messages, events), run read-only SQL queries:",
|
|
47
|
+
dbQueryCommand,
|
|
48
|
+
"Key tables: tasks, conversation_threads, conversation_turns, conversation_messages, conversation_events, agents.",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
chatParts.push(
|
|
52
|
+
"Use exploratory probing when useful to understand existing code, context, and constraints before acting.",
|
|
53
|
+
"Answer clearly and concisely.",
|
|
54
|
+
reasoningLine,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const historyBlock = options.conversationHistory
|
|
58
|
+
? buildConversationHistoryBlock(options.conversationHistory)
|
|
59
|
+
: "";
|
|
60
|
+
if (historyBlock) {
|
|
61
|
+
chatParts.push(historyBlock);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
chatParts.push("User message:", cleanMessage);
|
|
65
|
+
|
|
66
|
+
return chatParts.join("\n\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (mode === "plan") {
|
|
70
|
+
const planProposeCommand = `${cli} task plan-propose --task '<JSON>' [--task '<JSON>' ...]`;
|
|
71
|
+
const listAgentsCommand = `${cli} list-agents`;
|
|
72
|
+
const askQuestionCommand = `${cli} task ask-question --question "<question>" [--details "<optional context>"] [--option "<choice>" --option "<choice>" ...]`;
|
|
73
|
+
const dbQueryCommand = `${cli} db-query --sql "<SELECT statement>"`;
|
|
74
|
+
const candidates = Array.isArray(options.planCandidates) ? options.planCandidates : [];
|
|
75
|
+
const candidateLines = candidates.map((candidate, index) => {
|
|
76
|
+
const desc = candidate.description ? `; description=${candidate.description}` : "";
|
|
77
|
+
return `${index + 1}. id=${candidate.id}; name=${candidate.name}${desc}; harness=${candidate.harness}; model=${candidate.modelLabel}`;
|
|
78
|
+
});
|
|
79
|
+
const workspaceInstruction = options.workspaceIsEmpty
|
|
80
|
+
? "Workspace appears empty. Skip exploratory probing and immediately produce the plan and proposal."
|
|
81
|
+
: "Use tools for read-only inspection and exploratory context gathering before finalizing the plan.";
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
"You are planning work for implementation agents.",
|
|
85
|
+
"Plan mode is planning-only.",
|
|
86
|
+
workspaceInstruction,
|
|
87
|
+
"The `context/` directory in the workspace may contain markdown knowledge files written by previous agents. Check it for relevant prior context before planning.",
|
|
88
|
+
"Do not execute implementation work and do not modify files.",
|
|
89
|
+
`Use this Pragma CLI command prefix: ${cli}`,
|
|
90
|
+
"",
|
|
91
|
+
"## Step 1: Evaluate whether clarification is needed",
|
|
92
|
+
"Before producing any plan, assess the user's request for ambiguities, missing context, or decisions that could reasonably go multiple ways.",
|
|
93
|
+
"If the request is unclear, underspecified, or you are uncertain about the right approach, ask the user a clarifying question:",
|
|
94
|
+
askQuestionCommand,
|
|
95
|
+
"Use --option flags when there are a small number of concrete choices (2-5). Omit --option for open-ended questions.",
|
|
96
|
+
"After asking a question, STOP immediately. Do not produce a plan. Do not submit a proposal. Output only the question and nothing else. Wait for the user to respond.",
|
|
97
|
+
"To inspect workspace state (tasks, events, threads, messages), run read-only SQL queries:",
|
|
98
|
+
dbQueryCommand,
|
|
99
|
+
"Key tables: tasks (id, title, status, assigned_to, plan), conversation_threads (id, mode, task_id), conversation_turns (id, thread_id, mode, status), conversation_messages (id, thread_id, role, content), conversation_events (id, thread_id, event_name, payload), agents (id, name, status, harness).",
|
|
100
|
+
"",
|
|
101
|
+
"## Step 2: Produce the plan (only when requirements are clear)",
|
|
102
|
+
"When the request is unambiguous and you have enough information, return a concrete, decision-complete plan in plain language.",
|
|
103
|
+
"Your full response will be stored as the plan and passed to the implementation agent(s).",
|
|
104
|
+
"If you need to inspect available agents, run:",
|
|
105
|
+
listAgentsCommand,
|
|
106
|
+
"Available worker candidates (use one of these ids for `recipient`):",
|
|
107
|
+
candidateLines.length > 0 ? candidateLines.join("\n") : "(none available)",
|
|
108
|
+
"",
|
|
109
|
+
"## Step 3: Submit the plan proposal",
|
|
110
|
+
"After producing the plan, submit a structured proposal with tasks using this CLI command:",
|
|
111
|
+
planProposeCommand,
|
|
112
|
+
"Each --task flag takes a JSON object with:",
|
|
113
|
+
'- "title": short task name',
|
|
114
|
+
'- "prompt": the full implementation prompt for that task',
|
|
115
|
+
'- "recipient": agent id from the candidate list above',
|
|
116
|
+
"",
|
|
117
|
+
"For multi-step work, submit multiple --task flags. Tasks execute in sequence — each starts when the previous is ready for review.",
|
|
118
|
+
"For single tasks, submit one --task flag.",
|
|
119
|
+
"You MUST call this command exactly once, after writing the plan.",
|
|
120
|
+
"",
|
|
121
|
+
"Rules:",
|
|
122
|
+
"- Choose valid worker ids from the candidate list.",
|
|
123
|
+
"- In the execution plan, direct task-specific deliverables (reports/docs/assets) to `outputs/$PRAGMA_TASK_ID/`, not `context/`.",
|
|
124
|
+
"- Reserve `context/` only for enduring project knowledge that should outlive a single task.",
|
|
125
|
+
"- Write the plan as your natural response with clear steps.",
|
|
126
|
+
reasoningLine,
|
|
127
|
+
"User request:",
|
|
128
|
+
cleanMessage,
|
|
129
|
+
].join("\n\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [
|
|
133
|
+
"You are executing a software task end-to-end.",
|
|
134
|
+
"Begin with exploratory probing as needed so you understand existing structure, context, and constraints.",
|
|
135
|
+
"Use tools as needed and provide a concise final result.",
|
|
136
|
+
reasoningLine,
|
|
137
|
+
"Task:",
|
|
138
|
+
cleanMessage,
|
|
139
|
+
].join("\n\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function buildOrchestratorPrompt(input: {
|
|
143
|
+
task: string;
|
|
144
|
+
candidates: WorkerCandidate[];
|
|
145
|
+
forcedRecipientAgentId?: string | null;
|
|
146
|
+
reasoningEffort?: ReasoningEffort;
|
|
147
|
+
pragmaCliCommand?: string;
|
|
148
|
+
skills?: Array<{ name: string; description: string | null }>;
|
|
149
|
+
}): string {
|
|
150
|
+
const forced = input.forcedRecipientAgentId?.trim() || "";
|
|
151
|
+
const reasoningLine = formatReasoningInstruction(input.reasoningEffort ?? "medium");
|
|
152
|
+
const cli = input.pragmaCliCommand?.trim() || "pragma";
|
|
153
|
+
const selectRecipientCommand = `${cli} task select-recipient --agent-id <candidate_id> --reason "<one sentence reason>"`;
|
|
154
|
+
const candidateLines = input.candidates.map((candidate, index) => {
|
|
155
|
+
const desc = candidate.description ? `; description=${candidate.description}` : "";
|
|
156
|
+
return `${index + 1}. id=${candidate.id}; name=${candidate.name}${desc}; harness=${candidate.harness}; model=${candidate.modelLabel}`;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const parts = [
|
|
160
|
+
"You are an Orchestrator.",
|
|
161
|
+
"Your only task is to pick the best worker agent for the task below.",
|
|
162
|
+
"Do not execute the task.",
|
|
163
|
+
`Use this Pragma CLI command prefix: ${cli}`,
|
|
164
|
+
"Call exactly one CLI command to persist the selected worker:",
|
|
165
|
+
selectRecipientCommand,
|
|
166
|
+
"Rules:",
|
|
167
|
+
"- You MUST execute the command exactly once.",
|
|
168
|
+
"- agent-id MUST be one of the listed candidate ids.",
|
|
169
|
+
"- reason must be one sentence.",
|
|
170
|
+
"- Do not output JSON tags or any RECIPIENT payload.",
|
|
171
|
+
reasoningLine,
|
|
172
|
+
forced
|
|
173
|
+
? `- A recipient was manually requested. You MUST choose this exact agent id: ${forced}`
|
|
174
|
+
: "- If no candidate is suitable, still choose the closest match.",
|
|
175
|
+
"Task:",
|
|
176
|
+
input.task.trim(),
|
|
177
|
+
"Candidate workers:",
|
|
178
|
+
candidateLines.length > 0 ? candidateLines.join("\n") : "(none)",
|
|
179
|
+
"After the command succeeds, return a concise plain-text confirmation.",
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const skillIndex = formatSkillIndex(input.skills, cli);
|
|
183
|
+
if (skillIndex) {
|
|
184
|
+
parts.push(skillIndex);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return parts.join("\n\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function buildWorkerPrompt(input: {
|
|
191
|
+
task: string;
|
|
192
|
+
workerName: string;
|
|
193
|
+
workerAgentFile: string;
|
|
194
|
+
reasoningEffort?: ReasoningEffort;
|
|
195
|
+
pragmaCliCommand?: string;
|
|
196
|
+
preferredCodePath?: string | null;
|
|
197
|
+
taskWorkspaceDir?: string;
|
|
198
|
+
skills?: Array<{ name: string; description: string | null }>;
|
|
199
|
+
contextIndex?: string;
|
|
200
|
+
}): string {
|
|
201
|
+
const agentFile = input.workerAgentFile.trim();
|
|
202
|
+
const task = input.task.trim();
|
|
203
|
+
const reasoningLine = formatReasoningInstruction(input.reasoningEffort ?? "medium");
|
|
204
|
+
const cli = input.pragmaCliCommand?.trim() || "pragma";
|
|
205
|
+
const preferredCodePath = input.preferredCodePath?.trim() || "";
|
|
206
|
+
const taskWorkspaceDir = input.taskWorkspaceDir?.trim() || "";
|
|
207
|
+
const askQuestionCommand = `${cli} task ask-question --question "<question>" [--details "<optional context>"] [--option "<choice>" --option "<choice>" ...]`;
|
|
208
|
+
const requestHelpCommand = `${cli} task request-help --summary "<short summary>" [--details "<optional context>"]`;
|
|
209
|
+
const submitTestsCommand = `${cli} task submit-test-commands --command "<test command>" --cwd "<run directory>" [--name "<button label>"]`;
|
|
210
|
+
const submitTestingConfigCommand = `${cli} task submit-testing-config --config '<JSON>'`;
|
|
211
|
+
const dbQueryCommand = `${cli} db-query --sql "<SELECT statement>"`;
|
|
212
|
+
const codePathPolicyLine = preferredCodePath
|
|
213
|
+
? taskWorkspaceDir
|
|
214
|
+
? `- Put code/source changes under \`${join(taskWorkspaceDir, preferredCodePath)}/\` (relative: \`${preferredCodePath}/\`) unless the task explicitly targets another repo inside this task workspace.`
|
|
215
|
+
: `- Put code/source changes under \`${preferredCodePath}/\` unless the task explicitly targets another repo.`
|
|
216
|
+
: taskWorkspaceDir
|
|
217
|
+
? `- Put code/source changes under \`${join(taskWorkspaceDir, "code")}/\`.`
|
|
218
|
+
: "- Put code/source changes under `code/`.";
|
|
219
|
+
const workspaceBoundaryLine = taskWorkspaceDir
|
|
220
|
+
? `- Active task workspace root (write boundary): \`${taskWorkspaceDir}/\`.`
|
|
221
|
+
: "- Active task workspace root (write boundary): current working directory.";
|
|
222
|
+
|
|
223
|
+
const parts = [
|
|
224
|
+
`You are ${input.workerName}.`,
|
|
225
|
+
"Follow your agent instructions exactly, then execute the task.",
|
|
226
|
+
"Use exploratory probing as needed to gather context before making changes.",
|
|
227
|
+
`Use this Pragma CLI command prefix: ${cli}`,
|
|
228
|
+
"Path policy:",
|
|
229
|
+
workspaceBoundaryLine,
|
|
230
|
+
"- NEVER read, edit, or write files outside the active task workspace root. Before every file edit, verify the absolute path starts with the workspace root above. If a search tool returns a path outside the workspace root, find the equivalent file under the workspace root instead — do not edit the external path.",
|
|
231
|
+
codePathPolicyLine,
|
|
232
|
+
"- Put non-code artifacts (docs, reports, generated assets) under `outputs/$PRAGMA_TASK_ID/`.",
|
|
233
|
+
"- Do not place source code files at workspace root.",
|
|
234
|
+
"Git workflow: You are working in an isolated git worktree on a task branch. Do not run git commit, push, or checkout. Your file changes are automatically committed and merged when the task is approved. Subdirectories under code/ may be independent git repos — run git commands inside them, not at the workspace root.",
|
|
235
|
+
"Before making changes, check if the project has uninstalled dependencies (e.g. missing node_modules/, .venv/, vendor/, etc.) and install them using the appropriate package manager.",
|
|
236
|
+
"If you need clarification from the human, run:",
|
|
237
|
+
askQuestionCommand,
|
|
238
|
+
"Use --option flags when there are a small number of concrete choices (2-5). Omit --option for open-ended questions.",
|
|
239
|
+
"If you are blocked and need human help, run:",
|
|
240
|
+
requestHelpCommand,
|
|
241
|
+
"If you changed code, submit at least one runnable validation command for the task window.",
|
|
242
|
+
`For richer testing UIs with multiple processes and panels, use \`submit-testing-config\`:`,
|
|
243
|
+
submitTestingConfigCommand,
|
|
244
|
+
`The config JSON has: \`processes\` (array of {name, command, cwd?, port?, ready_pattern?}) and \`panels\` (array of panel objects). Panel types: \`web-preview\` ({type, title, process, path?, devices?}), \`api-tester\` ({type, title, process, endpoints: [{method, path, description?, body?, headers?}]}), \`terminal\` ({type, title, command, cwd?}), \`log-viewer\` ({type, title, process}). Optional: \`setup\` (array of setup commands), \`layout\` ("tabs"|"grid").`,
|
|
245
|
+
`Example: \`--config '{"processes":[{"name":"server","command":"npm run dev","cwd":"code/my-app","port":3000,"ready_pattern":"ready on"}],"panels":[{"type":"web-preview","title":"App","process":"server"}]}'\``,
|
|
246
|
+
`Fallback: for simple single-command cases, use:`,
|
|
247
|
+
"Include the exact run directory for each command (for example: `--cwd \"code/default/my-app\"`):",
|
|
248
|
+
submitTestsCommand,
|
|
249
|
+
"Submit only commands the agent cannot fully validate by itself (for example interactive app/service run commands for human verification).",
|
|
250
|
+
"Do not submit lint/typecheck/build/test commands to the task window.",
|
|
251
|
+
"For app tasks, the first submitted command must run the app/service (for example dev/start script with explicit host/port).",
|
|
252
|
+
"Provide only commands the human can run safely in this workspace.",
|
|
253
|
+
"After either CLI escalation command, stop doing further work.",
|
|
254
|
+
"Do not ask for clarification/help only in plain text without calling the CLI.",
|
|
255
|
+
"To inspect workspace state (tasks, events, conversation history), run read-only SQL queries:",
|
|
256
|
+
dbQueryCommand,
|
|
257
|
+
reasoningLine,
|
|
258
|
+
"Shared context: the `context/` directory contains markdown knowledge files that help agents understand the project.",
|
|
259
|
+
"Context files should describe **enduring project knowledge** — things that are true about the codebase regardless of any single task. Good examples: overall architecture, how modules connect, deployment setup, testing conventions, non-obvious constraints. Bad examples: what a specific task changed, CSS values, implementation details that are obvious from reading the code. If a future agent could learn it in 30 seconds by reading the relevant file, it does not belong in context. Only create or update context files when you have genuine project-level insight to add. Most tasks should NOT produce context files.",
|
|
260
|
+
input.contextIndex ? `Current context files:\n${input.contextIndex}` : "(No context files yet.)",
|
|
261
|
+
"Agent instructions:",
|
|
262
|
+
agentFile || "(No agent file provided. Use pragmatic software engineering judgement.)",
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const skillIndex = formatSkillIndex(input.skills, cli);
|
|
266
|
+
if (skillIndex) {
|
|
267
|
+
parts.push(skillIndex);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
parts.push("Task:", task, "Return a concise final result.");
|
|
271
|
+
|
|
272
|
+
return parts.join("\n\n");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const MAX_HISTORY_CHARS = 50_000;
|
|
276
|
+
const MAX_SINGLE_MESSAGE_CHARS = 2_000;
|
|
277
|
+
|
|
278
|
+
export function buildConversationHistoryBlock(
|
|
279
|
+
messages: Array<{ role: string; content: string }>,
|
|
280
|
+
): string {
|
|
281
|
+
if (messages.length === 0) return "";
|
|
282
|
+
|
|
283
|
+
const lines: string[] = [];
|
|
284
|
+
let totalChars = 0;
|
|
285
|
+
|
|
286
|
+
for (const msg of messages) {
|
|
287
|
+
const label = msg.role === "assistant" ? "Assistant" : "User";
|
|
288
|
+
let content = msg.content.trim();
|
|
289
|
+
if (content.length > MAX_SINGLE_MESSAGE_CHARS) {
|
|
290
|
+
content = content.slice(0, MAX_SINGLE_MESSAGE_CHARS) + "... [truncated]";
|
|
291
|
+
}
|
|
292
|
+
const line = `${label}: ${content}`;
|
|
293
|
+
if (totalChars + line.length > MAX_HISTORY_CHARS) break;
|
|
294
|
+
lines.push(line);
|
|
295
|
+
totalChars += line.length;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (lines.length === 0) return "";
|
|
299
|
+
|
|
300
|
+
return [
|
|
301
|
+
"<conversation_history>",
|
|
302
|
+
...lines,
|
|
303
|
+
"</conversation_history>",
|
|
304
|
+
"",
|
|
305
|
+
"Continue this conversation. The user's new message follows.",
|
|
306
|
+
].join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function formatSkillIndex(
|
|
310
|
+
skills: Array<{ name: string; description: string | null }> | undefined,
|
|
311
|
+
cli: string,
|
|
312
|
+
): string | null {
|
|
313
|
+
if (!skills || skills.length === 0) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const lines = skills.map((s) => `- ${s.name}: ${s.description || "(no description)"}`);
|
|
318
|
+
return [
|
|
319
|
+
`Available skills (use \`${cli} agent get-skill --name "<name>"\` to read full instructions):`,
|
|
320
|
+
...lines,
|
|
321
|
+
].join("\n");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function formatReasoningInstruction(reasoningEffort: ReasoningEffort): string {
|
|
325
|
+
if (reasoningEffort === "extra_high") {
|
|
326
|
+
return "Reasoning effort: extra high. Think deeply, consider alternatives, and verify assumptions.";
|
|
327
|
+
}
|
|
328
|
+
if (reasoningEffort === "high") {
|
|
329
|
+
return "Reasoning effort: high. Use thorough reasoning and check edge cases.";
|
|
330
|
+
}
|
|
331
|
+
if (reasoningEffort === "low") {
|
|
332
|
+
return "Reasoning effort: low. Prefer fast, direct reasoning and concise output.";
|
|
333
|
+
}
|
|
334
|
+
return "Reasoning effort: medium. Balance depth and speed.";
|
|
335
|
+
}
|