clementine-agent 1.18.55 → 1.18.57
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/cli/dashboard.js +68 -174
- package/dist/dashboard/builder/prompt.d.ts +36 -0
- package/dist/dashboard/builder/prompt.js +118 -0
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -18,6 +18,7 @@ import cron from 'node-cron';
|
|
|
18
18
|
import { TunnelManager } from './tunnel.js';
|
|
19
19
|
import { AgentManager } from '../agent/agent-manager.js';
|
|
20
20
|
import { discoverMcpServers, getClaudeIntegrations } from '../agent/mcp-bridge.js';
|
|
21
|
+
import { buildBuilderEnrichedMessage, builderSessionKey } from '../dashboard/builder/prompt.js';
|
|
21
22
|
import { AGENTS_DIR, SESSIONS_FILE, applyOneMillionContextRecovery, looksLikeClaudeOneMillionContextError, normalizeClaudeSdkOptionsForOneMillionContext, } from '../config.js';
|
|
22
23
|
import { parseTasks } from '../tools/shared.js';
|
|
23
24
|
import { todayISO } from '../gateway/cron-scheduler.js';
|
|
@@ -8019,100 +8020,29 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
8019
8020
|
}
|
|
8020
8021
|
});
|
|
8021
8022
|
// ── Builder chat endpoint ──────────────────────────────────────────
|
|
8022
|
-
// Track which builder sessions have received the full system prefix
|
|
8023
|
-
const builderSessionInited = new Set();
|
|
8024
8023
|
app.post('/api/builder/chat', async (req, res) => {
|
|
8025
8024
|
const { message, artifactType, agentSlug, currentArtifact, attachments, linkedTools } = req.body;
|
|
8026
8025
|
if (!message || typeof message !== 'string') {
|
|
8027
8026
|
res.status(400).json({ error: 'message is required' });
|
|
8028
8027
|
return;
|
|
8029
8028
|
}
|
|
8030
|
-
const
|
|
8031
|
-
const sessionKey = `dashboard:builder:${type}:${agentSlug || 'clementine'}`;
|
|
8032
|
-
const isFirstMessage = !builderSessionInited.has(sessionKey);
|
|
8033
|
-
// ── Artifact state (compact JSON — no pretty-print to save tokens) ──
|
|
8034
|
-
const artifactContext = currentArtifact
|
|
8035
|
-
? `\n[CURRENT ARTIFACT STATE]\n\`\`\`json-artifact\n${JSON.stringify(currentArtifact)}\n\`\`\`\n`
|
|
8036
|
-
: '';
|
|
8037
|
-
// ── File attachments — decode base64 text files and inject contents ──
|
|
8038
|
-
let fileContext = '';
|
|
8039
|
-
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
8040
|
-
const fileParts = [];
|
|
8041
|
-
for (const att of attachments) {
|
|
8042
|
-
if (att.filename && att.content) {
|
|
8043
|
-
try {
|
|
8044
|
-
const decoded = Buffer.from(att.content, 'base64').toString('utf-8');
|
|
8045
|
-
// Cap each file at 4K chars to keep context reasonable
|
|
8046
|
-
const trimmed = decoded.length > 4000 ? decoded.slice(0, 4000) + '\n... (truncated)' : decoded;
|
|
8047
|
-
fileParts.push(`### ${att.filename}\n\`\`\`\n${trimmed}\n\`\`\``);
|
|
8048
|
-
}
|
|
8049
|
-
catch { /* skip binary files */ }
|
|
8050
|
-
}
|
|
8051
|
-
}
|
|
8052
|
-
if (fileParts.length > 0) {
|
|
8053
|
-
fileContext = `\n[REFERENCE FILES — the user attached these for context]\n${fileParts.join('\n\n')}\n`;
|
|
8054
|
-
}
|
|
8055
|
-
}
|
|
8056
|
-
// ── Linked tools context ──
|
|
8057
|
-
let toolContext = '';
|
|
8058
|
-
if (Array.isArray(linkedTools) && linkedTools.length > 0) {
|
|
8059
|
-
toolContext = `\n[LINKED TOOLS — this skill should use these tools: ${linkedTools.join(', ')}]\n`;
|
|
8060
|
-
}
|
|
8061
|
-
// ── Build the enriched message ──
|
|
8062
|
-
let enrichedMessage;
|
|
8063
|
-
if (isFirstMessage) {
|
|
8064
|
-
// Full system prefix on first message only
|
|
8065
|
-
const agentContext = agentSlug ? `You are building this for the agent "${agentSlug}". The skill/cron will be scoped to this agent specifically.\n` : '';
|
|
8066
|
-
const builderPrefix = type === 'skill'
|
|
8067
|
-
? `[BUILDER MODE: You are helping build a reusable skill. ${agentContext}As you develop the procedure, output the current state as a JSON block:\n` +
|
|
8068
|
-
'```json-artifact\n{"type":"skill","title":"...","description":"...","triggers":["..."],"steps":"markdown procedure","toolsUsed":["tool1","tool2"]}\n```\n' +
|
|
8069
|
-
`Update this block in EVERY response as the skill evolves. If the user has linked tools, include them in the toolsUsed array. Ask clarifying questions to refine the procedure. Keep it conversational — one question at a time. ` +
|
|
8070
|
-
`When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
8071
|
-
: type === 'cron'
|
|
8072
|
-
? `[BUILDER MODE: You are helping build a scheduled cron job. As you develop the job, output the current state as a JSON block:\n` +
|
|
8073
|
-
'```json-artifact\n{"type":"cron","name":"...","schedule":"cron expression","tier":1,"prompt":"the full job prompt","mode":"standard","enabled":true}\n```\n' +
|
|
8074
|
-
`Update this block in EVERY response as the job evolves. Ask about schedule, what it should do, which tools/APIs it needs, what tier (1=read-only, 2=read-write), and whether it should run in unleashed mode.\n` +
|
|
8075
|
-
`IMPORTANT: Cron jobs automatically pull in matching skills (learned procedures) at runtime. If the user describes a workflow that should be reusable, suggest creating it as a skill first, then building the cron job that references those trigger keywords. This way the cron gets smarter over time as skills improve.\n` +
|
|
8076
|
-
`When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
8077
|
-
: type === 'agent'
|
|
8078
|
-
? `[BUILDER MODE: You are helping create a new AI agent team member. As you develop the agent config, output the current state as a JSON block:\n` +
|
|
8079
|
-
'```json-artifact\n{"type":"agent","name":"...","description":"role description","model":"sonnet","personality":"system prompt / onboarding brief","tools":["tool1","tool2"],"channel":"","tier":2}\n```\n' +
|
|
8080
|
-
`Update this block in EVERY response as the agent evolves. Ask about: the agent's role, what tools it needs, what model to use (haiku/sonnet/opus), its personality/system prompt, which channel it should operate in, and its security tier.\n` +
|
|
8081
|
-
`Help the user think about what makes a good agent: clear role, specific tools, focused personality. Keep it conversational — one question at a time.\n` +
|
|
8082
|
-
`When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
8083
|
-
: type === 'workflow'
|
|
8084
|
-
? `[BUILDER MODE: You are helping the user DRAFT a "trick" — a (possibly multi-step) thing Clementine can do on a schedule or on demand. You are NOT executing the trick. You are not running anything in the background. You are only authoring a spec the user will save, then run later from the dashboard.\n` +
|
|
8085
|
-
`\n` +
|
|
8086
|
-
`Hard rules:\n` +
|
|
8087
|
-
` - NEVER say "on it", "running in the background", "I'll follow up", "working on it now", or anything else that implies you're executing the user's request. You are drafting a spec.\n` +
|
|
8088
|
-
` - Stay strictly conversational. One short question per turn. Update the artifact block on every turn.\n` +
|
|
8089
|
-
` - If the user describes "real work" (multi-step actions, scrapers, enrichments, reports), still just draft it — don't dispatch.\n` +
|
|
8090
|
-
`\n` +
|
|
8091
|
-
`As you develop the trick, output the current state as a JSON block:\n` +
|
|
8092
|
-
'```json-artifact\n{"type":"workflow","name":"...","description":"...","schedule":"","model":"","steps":"step1:\\n prompt: ...\\nstep2:\\n prompt: ...\\n dependsOn: step1"}\n```\n' +
|
|
8093
|
-
`Ask about (in roughly this order, one at a time):\n` +
|
|
8094
|
-
` 1. The goal (one sentence is fine — confirm it back).\n` +
|
|
8095
|
-
` 2. When it should run — natural language is fine ("every weekday at 9"); convert to a cron expression in the schedule field. Empty schedule = manual.\n` +
|
|
8096
|
-
` 3. Which tools, projects, or channels she'll need (MCP servers, local CLIs like sf/gh/gcloud, Slack/Discord targets).\n` +
|
|
8097
|
-
` 4. Which model — claude-opus-4-7 (most capable), claude-sonnet-4-6 (balanced), or claude-haiku-4-5-20251001 (fastest). Leave model empty if the user doesn't care.\n` +
|
|
8098
|
-
`Most tricks need only one prompt step. Add steps only when the user explicitly wants a multi-step pipeline.\n` +
|
|
8099
|
-
`When the user says "save" or approves, output the final artifact block — don't try to save it yourself, the dashboard handles persistence.]\n\n`
|
|
8100
|
-
: `[BUILDER MODE: You are helping configure an artifact. Output structured JSON blocks as you build.]\n\n`;
|
|
8101
|
-
enrichedMessage = builderPrefix + fileContext + toolContext + artifactContext + message;
|
|
8102
|
-
builderSessionInited.add(sessionKey);
|
|
8103
|
-
}
|
|
8104
|
-
else {
|
|
8105
|
-
// Subsequent messages: just artifact state + files + tools + user message (no repeated prefix)
|
|
8106
|
-
enrichedMessage = fileContext + toolContext + artifactContext + message;
|
|
8107
|
-
}
|
|
8029
|
+
const sessionKey = builderSessionKey(artifactType, agentSlug);
|
|
8108
8030
|
try {
|
|
8109
8031
|
const gateway = await getGateway();
|
|
8110
|
-
//
|
|
8111
|
-
//
|
|
8112
|
-
//
|
|
8113
|
-
//
|
|
8114
|
-
|
|
8115
|
-
|
|
8032
|
+
// First-message detection uses the SDK session ID — set after the
|
|
8033
|
+
// first runAgent turn returns, persisted across daemon restarts.
|
|
8034
|
+
// Anchoring on this means we send the system prefix exactly once
|
|
8035
|
+
// per genuine new conversation, not once per process lifetime.
|
|
8036
|
+
const isFirstMessage = !gateway.assistant.getSdkSessionId(sessionKey);
|
|
8037
|
+
const enrichedMessage = buildBuilderEnrichedMessage({
|
|
8038
|
+
message,
|
|
8039
|
+
artifactType,
|
|
8040
|
+
agentSlug,
|
|
8041
|
+
currentArtifact,
|
|
8042
|
+
attachments,
|
|
8043
|
+
linkedTools,
|
|
8044
|
+
isFirstMessage,
|
|
8045
|
+
});
|
|
8116
8046
|
const response = await gateway.handleMessage(sessionKey, enrichedMessage);
|
|
8117
8047
|
// Parse any json-artifact blocks from the response
|
|
8118
8048
|
let artifact = null;
|
|
@@ -8165,84 +8095,20 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
8165
8095
|
// before the gateway warms up (otherwise some HTTP intermediaries hold
|
|
8166
8096
|
// the response until first body byte).
|
|
8167
8097
|
writeEvent('progress', { status: 'connecting…' });
|
|
8168
|
-
|
|
8169
|
-
// artifact + files + tools on every turn). Inlined to keep the diff
|
|
8170
|
-
// contained; refactor into a helper if a third endpoint shows up.
|
|
8171
|
-
const type = artifactType || 'skill';
|
|
8172
|
-
const sessionKey = `dashboard:builder:${type}:${agentSlug || 'clementine'}`;
|
|
8173
|
-
const isFirstMessage = !builderSessionInited.has(sessionKey);
|
|
8174
|
-
const artifactContext = currentArtifact
|
|
8175
|
-
? `\n[CURRENT ARTIFACT STATE]\n\`\`\`json-artifact\n${JSON.stringify(currentArtifact)}\n\`\`\`\n`
|
|
8176
|
-
: '';
|
|
8177
|
-
let fileContext = '';
|
|
8178
|
-
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
8179
|
-
const fileParts = [];
|
|
8180
|
-
for (const att of attachments) {
|
|
8181
|
-
if (att.filename && att.content) {
|
|
8182
|
-
try {
|
|
8183
|
-
const decoded = Buffer.from(att.content, 'base64').toString('utf-8');
|
|
8184
|
-
const trimmed = decoded.length > 4000 ? decoded.slice(0, 4000) + '\n... (truncated)' : decoded;
|
|
8185
|
-
fileParts.push(`### ${att.filename}\n\`\`\`\n${trimmed}\n\`\`\``);
|
|
8186
|
-
}
|
|
8187
|
-
catch { /* skip binary files */ }
|
|
8188
|
-
}
|
|
8189
|
-
}
|
|
8190
|
-
if (fileParts.length > 0) {
|
|
8191
|
-
fileContext = `\n[REFERENCE FILES — the user attached these for context]\n${fileParts.join('\n\n')}\n`;
|
|
8192
|
-
}
|
|
8193
|
-
}
|
|
8194
|
-
let toolContext = '';
|
|
8195
|
-
if (Array.isArray(linkedTools) && linkedTools.length > 0) {
|
|
8196
|
-
toolContext = `\n[LINKED TOOLS — this skill should use these tools: ${linkedTools.join(', ')}]\n`;
|
|
8197
|
-
}
|
|
8198
|
-
let enrichedMessage;
|
|
8199
|
-
if (isFirstMessage) {
|
|
8200
|
-
const agentContext = agentSlug ? `You are building this for the agent "${agentSlug}". The skill/cron will be scoped to this agent specifically.\n` : '';
|
|
8201
|
-
const builderPrefix = type === 'skill'
|
|
8202
|
-
? `[BUILDER MODE: You are helping build a reusable skill. ${agentContext}As you develop the procedure, output the current state as a JSON block:\n` +
|
|
8203
|
-
'```json-artifact\n{"type":"skill","title":"...","description":"...","triggers":["..."],"steps":"markdown procedure","toolsUsed":["tool1","tool2"]}\n```\n' +
|
|
8204
|
-
`Update this block in EVERY response as the skill evolves. If the user has linked tools, include them in the toolsUsed array. Ask clarifying questions to refine the procedure. Keep it conversational — one question at a time. ` +
|
|
8205
|
-
`When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
8206
|
-
: type === 'cron'
|
|
8207
|
-
? `[BUILDER MODE: You are helping build a scheduled cron job. As you develop the job, output the current state as a JSON block:\n` +
|
|
8208
|
-
'```json-artifact\n{"type":"cron","name":"...","schedule":"cron expression","tier":1,"prompt":"the full job prompt","mode":"standard","enabled":true}\n```\n' +
|
|
8209
|
-
`Update this block in EVERY response as the job evolves. Ask about schedule, what it should do, which tools/APIs it needs, what tier (1=read-only, 2=read-write), and whether it should run in unleashed mode.\n` +
|
|
8210
|
-
`When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
8211
|
-
: type === 'agent'
|
|
8212
|
-
? `[BUILDER MODE: You are helping create a new AI agent team member. As you develop the agent config, output the current state as a JSON block:\n` +
|
|
8213
|
-
'```json-artifact\n{"type":"agent","name":"...","description":"role description","model":"sonnet","personality":"system prompt / onboarding brief","tools":["tool1","tool2"],"channel":"","tier":2}\n```\n' +
|
|
8214
|
-
`Update this block in EVERY response as the agent evolves. Ask about: the agent's role, what tools it needs, what model to use, its personality/system prompt, which channel it should operate in, and its security tier.\n` +
|
|
8215
|
-
`Keep it conversational — one question at a time. When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
8216
|
-
: type === 'workflow'
|
|
8217
|
-
? `[BUILDER MODE: You are helping the user DRAFT a "trick" — a (possibly multi-step) thing Clementine can do on a schedule or on demand. You are NOT executing the trick. You are not running anything in the background. You are only authoring a spec the user will save, then run later from the dashboard.\n` +
|
|
8218
|
-
`\n` +
|
|
8219
|
-
`Hard rules:\n` +
|
|
8220
|
-
` - NEVER say "on it", "running in the background", "I'll follow up", "working on it now", or anything else that implies you're executing the user's request. You are drafting a spec.\n` +
|
|
8221
|
-
` - Stay strictly conversational. One short question per turn. Update the artifact block on every turn.\n` +
|
|
8222
|
-
` - If the user describes "real work" (multi-step actions, scrapers, enrichments, reports), still just draft it — don't dispatch.\n` +
|
|
8223
|
-
`\n` +
|
|
8224
|
-
`As you develop the trick, output the current state as a JSON block:\n` +
|
|
8225
|
-
'```json-artifact\n{"type":"workflow","name":"...","description":"...","schedule":"","model":"","steps":"step1:\\n prompt: ...\\nstep2:\\n prompt: ...\\n dependsOn: step1"}\n```\n' +
|
|
8226
|
-
`Ask about (in roughly this order, one at a time):\n` +
|
|
8227
|
-
` 1. The goal (one sentence is fine — confirm it back).\n` +
|
|
8228
|
-
` 2. When it should run — natural language is fine ("every weekday at 9"); convert to a cron expression in the schedule field. Empty schedule = manual.\n` +
|
|
8229
|
-
` 3. Which tools, projects, or channels she'll need (MCP servers, local CLIs like sf/gh/gcloud, Slack/Discord targets).\n` +
|
|
8230
|
-
` 4. Which model — claude-opus-4-7 (most capable), claude-sonnet-4-6 (balanced), or claude-haiku-4-5-20251001 (fastest). Leave model empty if the user doesn't care.\n` +
|
|
8231
|
-
`Most tricks need only one prompt step. Add steps only when the user explicitly wants a multi-step pipeline.\n` +
|
|
8232
|
-
`When the user says "save" or approves, output the final artifact block — don't try to save it yourself, the dashboard handles persistence.]\n\n`
|
|
8233
|
-
: `[BUILDER MODE: You are helping configure an artifact. Output structured JSON blocks as you build.]\n\n`;
|
|
8234
|
-
enrichedMessage = builderPrefix + fileContext + toolContext + artifactContext + message;
|
|
8235
|
-
builderSessionInited.add(sessionKey);
|
|
8236
|
-
}
|
|
8237
|
-
else {
|
|
8238
|
-
enrichedMessage = fileContext + toolContext + artifactContext + message;
|
|
8239
|
-
}
|
|
8098
|
+
const sessionKey = builderSessionKey(artifactType, agentSlug);
|
|
8240
8099
|
try {
|
|
8241
8100
|
writeEvent('progress', { status: 'thinking…' });
|
|
8242
8101
|
const gateway = await getGateway();
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8102
|
+
const isFirstMessage = !gateway.assistant.getSdkSessionId(sessionKey);
|
|
8103
|
+
const enrichedMessage = buildBuilderEnrichedMessage({
|
|
8104
|
+
message,
|
|
8105
|
+
artifactType,
|
|
8106
|
+
agentSlug,
|
|
8107
|
+
currentArtifact,
|
|
8108
|
+
attachments,
|
|
8109
|
+
linkedTools,
|
|
8110
|
+
isFirstMessage,
|
|
8111
|
+
});
|
|
8246
8112
|
let lastText = '';
|
|
8247
8113
|
const response = await gateway.handleMessage(sessionKey, enrichedMessage, async (text) => {
|
|
8248
8114
|
lastText = text ?? '';
|
|
@@ -8277,11 +8143,14 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
8277
8143
|
}
|
|
8278
8144
|
});
|
|
8279
8145
|
// Reset builder session when user clicks "New"
|
|
8280
|
-
app.post('/api/builder/reset', (_req, res) => {
|
|
8146
|
+
app.post('/api/builder/reset', async (_req, res) => {
|
|
8281
8147
|
const { artifactType, agentSlug } = _req.body;
|
|
8282
|
-
const
|
|
8283
|
-
|
|
8284
|
-
|
|
8148
|
+
const sessionKey = builderSessionKey(artifactType, agentSlug);
|
|
8149
|
+
// Clearing the SDK session ID is what makes the next turn detect
|
|
8150
|
+
// as "first" and re-emit the system prefix. The per-process Set
|
|
8151
|
+
// has been replaced by the persisted SDK session map.
|
|
8152
|
+
const gateway = await getGateway();
|
|
8153
|
+
gateway.assistant.clearSession(sessionKey);
|
|
8285
8154
|
res.json({ ok: true });
|
|
8286
8155
|
});
|
|
8287
8156
|
// Test a skill by sending a trigger message through the gateway with skill context
|
|
@@ -26037,10 +25906,10 @@ function renderBuilderPreview(artifact, type) {
|
|
|
26037
25906
|
+ '<option value="2"' + (artifact.tier === 2 ? ' selected' : '') + '>2 — Read+Write</option>'
|
|
26038
25907
|
+ '<option value="3"' + (artifact.tier === 3 ? ' selected' : '') + '>3 — Full</option>'
|
|
26039
25908
|
+ '</select></div>'
|
|
26040
|
-
+ '<div class="preview-field"><label>
|
|
26041
|
-
+ '<
|
|
26042
|
-
+ '<
|
|
26043
|
-
+ '</
|
|
25909
|
+
+ '<div class="preview-field"><label>Linked Tools</label>'
|
|
25910
|
+
+ '<div id="builder-tools-panel" style="max-height:180px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:6px 8px;background:var(--bg-primary);margin-bottom:4px"></div>'
|
|
25911
|
+
+ '<div style="font-size:10px;color:var(--text-muted)">Pick tools the cron should use. The chat sees these as a hint, and they steer how the prompt is written.</div>'
|
|
25912
|
+
+ '</div>'
|
|
26044
25913
|
+ '<div class="preview-field"><label>Prompt</label><textarea rows="12" onchange="builderArtifact.prompt=this.value">' + esc(artifact.prompt || '') + '</textarea></div>'
|
|
26045
25914
|
+ '<div class="preview-field"><label>Reference Files</label>'
|
|
26046
25915
|
+ '<div id="builder-attachments-list"></div>'
|
|
@@ -26050,6 +25919,7 @@ function renderBuilderPreview(artifact, type) {
|
|
|
26050
25919
|
+ '</label>'
|
|
26051
25920
|
+ '<div style="font-size:10px;color:var(--text-muted);margin-top:4px">Files injected into the agent prompt at runtime</div>'
|
|
26052
25921
|
+ '</div>';
|
|
25922
|
+
setTimeout(function() { loadBuilderToolOptions(artifact.toolsUsed || _builderLinkedTools); }, 50);
|
|
26053
25923
|
} else if (type === 'agent') {
|
|
26054
25924
|
html = '<div class="preview-field"><label>Name</label><input type="text" value="' + esc(artifact.name || '') + '" onchange="builderArtifact.name=this.value"></div>'
|
|
26055
25925
|
+ '<div class="preview-field"><label>Description / Role</label><input type="text" value="' + esc(artifact.description || '') + '" onchange="builderArtifact.description=this.value"></div>'
|
|
@@ -26060,13 +25930,22 @@ function renderBuilderPreview(artifact, type) {
|
|
|
26060
25930
|
+ '<option value="opus"' + (artifact.model === 'opus' ? ' selected' : '') + '>Opus</option>'
|
|
26061
25931
|
+ '</select></div>'
|
|
26062
25932
|
+ '<div class="preview-field"><label>Personality / System Prompt</label><textarea rows="8" onchange="builderArtifact.personality=this.value">' + esc(artifact.personality || '') + '</textarea></div>'
|
|
26063
|
-
+ '<div class="preview-field"><label>Tools
|
|
25933
|
+
+ '<div class="preview-field"><label>Allowed Tools</label>'
|
|
25934
|
+
+ '<div id="builder-tools-panel" style="max-height:180px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:6px 8px;background:var(--bg-primary);margin-bottom:4px"></div>'
|
|
25935
|
+
+ '<div style="font-size:10px;color:var(--text-muted)">These become the agent\\x27s allowedTools — they\\x27re honored at the main-agent level when this agent runs as a profile.</div>'
|
|
25936
|
+
+ '</div>'
|
|
26064
25937
|
+ '<div class="preview-field"><label>Channel</label><input type="text" value="' + esc(artifact.channel || '') + '" onchange="builderArtifact.channel=this.value" placeholder="e.g. discord, slack"></div>';
|
|
25938
|
+
setTimeout(function() { loadBuilderToolOptions(artifact.tools || _builderLinkedTools); }, 50);
|
|
26065
25939
|
} else if (type === 'workflow') {
|
|
26066
25940
|
html = '<div class="preview-field"><label>Workflow Name</label><input type="text" value="' + esc(artifact.name || '') + '" onchange="builderArtifact.name=this.value"></div>'
|
|
26067
25941
|
+ '<div class="preview-field"><label>Description</label><input type="text" value="' + esc(artifact.description || '') + '" onchange="builderArtifact.description=this.value"></div>'
|
|
26068
25942
|
+ '<div class="preview-field"><label>Trigger Schedule (cron, optional)</label><input type="text" value="' + esc(artifact.schedule || '') + '" onchange="builderArtifact.schedule=this.value" placeholder="e.g. 0 9 * * 1 (Mondays at 9am)"></div>'
|
|
25943
|
+
+ '<div class="preview-field"><label>Linked Tools</label>'
|
|
25944
|
+
+ '<div id="builder-tools-panel" style="max-height:180px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:6px 8px;background:var(--bg-primary);margin-bottom:4px"></div>'
|
|
25945
|
+
+ '<div style="font-size:10px;color:var(--text-muted)">Tools the trick will use. The chat sees these as a hint and weaves them into the steps.</div>'
|
|
25946
|
+
+ '</div>'
|
|
26069
25947
|
+ '<div class="preview-field"><label>Steps (YAML/Markdown)</label><textarea rows="14" onchange="builderArtifact.steps=this.value">' + esc(artifact.steps || '') + '</textarea></div>';
|
|
25948
|
+
setTimeout(function() { loadBuilderToolOptions(artifact.toolsUsed || _builderLinkedTools); }, 50);
|
|
26070
25949
|
}
|
|
26071
25950
|
preview.innerHTML = html;
|
|
26072
25951
|
// Load existing attachments if editing
|
|
@@ -26124,8 +26003,13 @@ async function saveBuilderArtifact() {
|
|
|
26124
26003
|
var type = document.getElementById('builder-type').value;
|
|
26125
26004
|
try {
|
|
26126
26005
|
var agentSlug = (document.getElementById('builder-agent') || {}).value || '';
|
|
26127
|
-
// Sync linked tools into artifact before saving
|
|
26128
|
-
|
|
26006
|
+
// Sync linked tools into artifact before saving — picker is shared
|
|
26007
|
+
// across types but the persisted field name differs.
|
|
26008
|
+
if (type === 'agent') {
|
|
26009
|
+
builderArtifact.tools = _builderLinkedTools;
|
|
26010
|
+
} else {
|
|
26011
|
+
builderArtifact.toolsUsed = _builderLinkedTools;
|
|
26012
|
+
}
|
|
26129
26013
|
var r = await apiJson('POST', '/api/builder/save', {
|
|
26130
26014
|
artifactType: type,
|
|
26131
26015
|
artifact: builderArtifact,
|
|
@@ -26212,8 +26096,18 @@ async function loadBuilderToolOptions(selectedTools) {
|
|
|
26212
26096
|
|
|
26213
26097
|
function syncBuilderLinkedTools() {
|
|
26214
26098
|
_builderLinkedTools = Array.from(document.querySelectorAll('.builder-tool-cb:checked')).map(function(cb) { return cb.value; });
|
|
26215
|
-
|
|
26216
|
-
|
|
26099
|
+
if (!builderArtifact) return;
|
|
26100
|
+
// Field name differs per artifact type: agent stores into "tools"
|
|
26101
|
+
// (which becomes profile.team.allowedTools on save). Skill, cron and
|
|
26102
|
+
// workflow store into "toolsUsed" — carried for reference, since the
|
|
26103
|
+
// runtime LINKED TOOLS context block is rebuilt from picker state
|
|
26104
|
+
// each turn.
|
|
26105
|
+
var type = (document.getElementById('builder-type') || {}).value;
|
|
26106
|
+
if (type === 'agent') {
|
|
26107
|
+
builderArtifact.tools = _builderLinkedTools;
|
|
26108
|
+
} else {
|
|
26109
|
+
builderArtifact.toolsUsed = _builderLinkedTools;
|
|
26110
|
+
}
|
|
26217
26111
|
}
|
|
26218
26112
|
|
|
26219
26113
|
// ── Builder File Attachments ──────────────
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type BuilderType = 'skill' | 'cron' | 'agent' | 'workflow';
|
|
2
|
+
export interface BuildBuilderMessageOptions {
|
|
3
|
+
/** What the user typed in the chat box. */
|
|
4
|
+
message: string;
|
|
5
|
+
/** Type of artifact being drafted. Defaults to 'skill'. */
|
|
6
|
+
artifactType?: string;
|
|
7
|
+
/** When set, the artifact is scoped to this hired agent. */
|
|
8
|
+
agentSlug?: string;
|
|
9
|
+
/** Current artifact JSON the dashboard is holding. Re-sent each
|
|
10
|
+
* turn so the agent has the live state in front of it. */
|
|
11
|
+
currentArtifact?: unknown;
|
|
12
|
+
/** Files the user dragged in for context. Each entry has a
|
|
13
|
+
* filename + base64-encoded content. Decoded + capped per file. */
|
|
14
|
+
attachments?: Array<{
|
|
15
|
+
filename?: string;
|
|
16
|
+
content?: string;
|
|
17
|
+
}>;
|
|
18
|
+
/** Pre-selected MCP / local tool names the artifact should use. */
|
|
19
|
+
linkedTools?: string[];
|
|
20
|
+
/** Whether this is the first turn of a new conversation (no prior
|
|
21
|
+
* SDK session). When true, the system prefix is included. */
|
|
22
|
+
isFirstMessage: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compose the message that gets sent to gateway.handleMessage. The
|
|
26
|
+
* shape is: optional system prefix (first turn only), then file
|
|
27
|
+
* context, then linked-tools context, then current artifact state,
|
|
28
|
+
* then the user's literal message.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildBuilderEnrichedMessage(opts: BuildBuilderMessageOptions): string;
|
|
31
|
+
/**
|
|
32
|
+
* Stable session-key for a builder conversation. Same key across
|
|
33
|
+
* /api/builder/chat and /api/builder/chat/stream so they share state.
|
|
34
|
+
*/
|
|
35
|
+
export declare function builderSessionKey(artifactType: string | undefined, agentSlug: string | undefined): string;
|
|
36
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the enriched user message the trick builder sends to the
|
|
3
|
+
* canonical chat path. Single source of truth shared by both
|
|
4
|
+
* /api/builder/chat and /api/builder/chat/stream.
|
|
5
|
+
*
|
|
6
|
+
* Two non-obvious things:
|
|
7
|
+
*
|
|
8
|
+
* 1. "First message" detection uses the SDK session ID, not an
|
|
9
|
+
* in-memory Set. Daemon restart wipes in-memory state, so the
|
|
10
|
+
* prior implementation re-sent the system prefix on the very next
|
|
11
|
+
* turn even though SDK session resume kept the agent's memory of
|
|
12
|
+
* it. Anchoring detection to the persisted SDK session means
|
|
13
|
+
* the prefix is sent exactly once per genuine new conversation.
|
|
14
|
+
*
|
|
15
|
+
* 2. Model names referenced in the workflow prefix come from the
|
|
16
|
+
* MODELS config so they don't rot when models advance.
|
|
17
|
+
*/
|
|
18
|
+
import { MODELS } from '../../config.js';
|
|
19
|
+
const FILE_MAX_CHARS = 4000;
|
|
20
|
+
function buildArtifactContext(currentArtifact) {
|
|
21
|
+
if (!currentArtifact)
|
|
22
|
+
return '';
|
|
23
|
+
return `\n[CURRENT ARTIFACT STATE]\n\`\`\`json-artifact\n${JSON.stringify(currentArtifact)}\n\`\`\`\n`;
|
|
24
|
+
}
|
|
25
|
+
function buildFileContext(attachments) {
|
|
26
|
+
if (!Array.isArray(attachments) || attachments.length === 0)
|
|
27
|
+
return '';
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (const att of attachments) {
|
|
30
|
+
if (!att.filename || !att.content)
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
const decoded = Buffer.from(att.content, 'base64').toString('utf-8');
|
|
34
|
+
const trimmed = decoded.length > FILE_MAX_CHARS
|
|
35
|
+
? decoded.slice(0, FILE_MAX_CHARS) + '\n... (truncated)'
|
|
36
|
+
: decoded;
|
|
37
|
+
parts.push(`### ${att.filename}\n\`\`\`\n${trimmed}\n\`\`\``);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Binary file; skip.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (parts.length === 0)
|
|
44
|
+
return '';
|
|
45
|
+
return `\n[REFERENCE FILES — the user attached these for context]\n${parts.join('\n\n')}\n`;
|
|
46
|
+
}
|
|
47
|
+
function buildToolContext(linkedTools) {
|
|
48
|
+
if (!Array.isArray(linkedTools) || linkedTools.length === 0)
|
|
49
|
+
return '';
|
|
50
|
+
return `\n[LINKED TOOLS — this artifact should use these tools: ${linkedTools.join(', ')}]\n`;
|
|
51
|
+
}
|
|
52
|
+
function buildSystemPrefix(type, agentSlug) {
|
|
53
|
+
const agentContext = agentSlug
|
|
54
|
+
? `You are building this for the agent "${agentSlug}". The artifact will be scoped to this agent specifically.\n`
|
|
55
|
+
: '';
|
|
56
|
+
if (type === 'skill') {
|
|
57
|
+
return `[BUILDER MODE: You are helping build a reusable skill. ${agentContext}As you develop the procedure, output the current state as a JSON block:\n` +
|
|
58
|
+
'```json-artifact\n{"type":"skill","title":"...","description":"...","triggers":["..."],"steps":"markdown procedure","toolsUsed":["tool1","tool2"]}\n```\n' +
|
|
59
|
+
`Update this block in EVERY response as the skill evolves. If the user has linked tools, include them in the toolsUsed array. Ask clarifying questions to refine the procedure. Keep it conversational — one question at a time. ` +
|
|
60
|
+
`When the user says "save" or approves, output the final artifact block.]\n\n`;
|
|
61
|
+
}
|
|
62
|
+
if (type === 'cron') {
|
|
63
|
+
return `[BUILDER MODE: You are helping build a scheduled cron job. ${agentContext}As you develop the job, output the current state as a JSON block:\n` +
|
|
64
|
+
'```json-artifact\n{"type":"cron","name":"...","schedule":"cron expression","tier":1,"prompt":"the full job prompt","enabled":true}\n```\n' +
|
|
65
|
+
`Update this block in EVERY response as the job evolves. Ask about schedule, what it should do, which tools/APIs it needs, and what tier (1=read-only, 2=read-write). ` +
|
|
66
|
+
`Cron jobs automatically pull in matching skills (learned procedures) at runtime. If the user describes a workflow that should be reusable, suggest creating it as a skill first, then building the cron job that references those trigger keywords.\n` +
|
|
67
|
+
`When the user says "save" or approves, output the final artifact block.]\n\n`;
|
|
68
|
+
}
|
|
69
|
+
if (type === 'agent') {
|
|
70
|
+
return `[BUILDER MODE: You are helping create a new AI agent team member. As you develop the agent config, output the current state as a JSON block:\n` +
|
|
71
|
+
'```json-artifact\n{"type":"agent","name":"...","description":"role description","model":"sonnet","personality":"system prompt / onboarding brief","tools":["tool1","tool2"],"channel":"","tier":2}\n```\n' +
|
|
72
|
+
`Update this block in EVERY response as the agent evolves. Ask about: the agent's role, what tools it needs, what model to use (haiku/sonnet/opus), its personality/system prompt, which channel it should operate in, and its security tier.\n` +
|
|
73
|
+
`Help the user think about what makes a good agent: clear role, specific tools, focused personality. Keep it conversational — one question at a time.\n` +
|
|
74
|
+
`When the user says "save" or approves, output the final artifact block.]\n\n`;
|
|
75
|
+
}
|
|
76
|
+
if (type === 'workflow') {
|
|
77
|
+
return `[BUILDER MODE: You are helping the user DRAFT a "trick" — a (possibly multi-step) thing Clementine can do on a schedule or on demand. You are NOT executing the trick. You are not running anything in the background. You are only authoring a spec the user will save, then run later from the dashboard.\n` +
|
|
78
|
+
`\n` +
|
|
79
|
+
`Hard rules:\n` +
|
|
80
|
+
` - NEVER say "on it", "running in the background", "I'll follow up", "working on it now", or anything else that implies you're executing the user's request. You are drafting a spec.\n` +
|
|
81
|
+
` - Stay strictly conversational. One short question per turn. Update the artifact block on every turn.\n` +
|
|
82
|
+
` - If the user describes "real work" (multi-step actions, scrapers, enrichments, reports), still just draft it — don't dispatch.\n` +
|
|
83
|
+
`\n` +
|
|
84
|
+
`As you develop the trick, output the current state as a JSON block:\n` +
|
|
85
|
+
'```json-artifact\n{"type":"workflow","name":"...","description":"...","schedule":"","model":"","steps":"step1:\\n prompt: ...\\nstep2:\\n prompt: ...\\n dependsOn: step1"}\n```\n' +
|
|
86
|
+
`Ask about (in roughly this order, one at a time):\n` +
|
|
87
|
+
` 1. The goal (one sentence is fine — confirm it back).\n` +
|
|
88
|
+
` 2. When it should run — natural language is fine ("every weekday at 9"); convert to a cron expression in the schedule field. Empty schedule = manual.\n` +
|
|
89
|
+
` 3. Which tools, projects, or channels she'll need (MCP servers, local CLIs like sf/gh/gcloud, Slack/Discord targets).\n` +
|
|
90
|
+
` 4. Which model — ${MODELS.opus} (most capable), ${MODELS.sonnet} (balanced), or ${MODELS.haiku} (fastest). Leave model empty if the user doesn't care.\n` +
|
|
91
|
+
`Most tricks need only one prompt step. Add steps only when the user explicitly wants a multi-step pipeline.\n` +
|
|
92
|
+
`When the user says "save" or approves, output the final artifact block — don't try to save it yourself, the dashboard handles persistence.]\n\n`;
|
|
93
|
+
}
|
|
94
|
+
return `[BUILDER MODE: You are helping configure an artifact. Output structured JSON blocks as you build.]\n\n`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Compose the message that gets sent to gateway.handleMessage. The
|
|
98
|
+
* shape is: optional system prefix (first turn only), then file
|
|
99
|
+
* context, then linked-tools context, then current artifact state,
|
|
100
|
+
* then the user's literal message.
|
|
101
|
+
*/
|
|
102
|
+
export function buildBuilderEnrichedMessage(opts) {
|
|
103
|
+
const type = opts.artifactType || 'skill';
|
|
104
|
+
const fileContext = buildFileContext(opts.attachments);
|
|
105
|
+
const toolContext = buildToolContext(opts.linkedTools);
|
|
106
|
+
const artifactContext = buildArtifactContext(opts.currentArtifact);
|
|
107
|
+
const prefix = opts.isFirstMessage ? buildSystemPrefix(type, opts.agentSlug) : '';
|
|
108
|
+
return prefix + fileContext + toolContext + artifactContext + opts.message;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stable session-key for a builder conversation. Same key across
|
|
112
|
+
* /api/builder/chat and /api/builder/chat/stream so they share state.
|
|
113
|
+
*/
|
|
114
|
+
export function builderSessionKey(artifactType, agentSlug) {
|
|
115
|
+
const type = artifactType || 'skill';
|
|
116
|
+
return `dashboard:builder:${type}:${agentSlug || 'clementine'}`;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=prompt.js.map
|