acdev 1.0.15 → 1.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/package.json +1 -1
- package/public/app.js +163 -8
- package/public/index.html +16 -0
- package/public/styles.css +62 -6
- package/src/agent.js +12 -0
- package/src/server.js +16 -0
- package/src/store.js +11 -8
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -240,6 +240,7 @@ const els = {
|
|
|
240
240
|
overviewTicketSource: document.getElementById('overview-ticket-source'),
|
|
241
241
|
enqueueFeedback: document.getElementById('enqueue-feedback'),
|
|
242
242
|
preferredBranch: document.getElementById('preferred-branch'),
|
|
243
|
+
enqueueUserPrompt: document.getElementById('enqueue-user-prompt'),
|
|
243
244
|
addBtn: document.getElementById('add-btn'),
|
|
244
245
|
statsGrid: document.getElementById('stats-grid'),
|
|
245
246
|
overviewRunning: document.getElementById('overview-running'),
|
|
@@ -301,6 +302,8 @@ const els = {
|
|
|
301
302
|
settingsModelValue: document.getElementById('settings-model-value'),
|
|
302
303
|
settingsModelPanel: document.getElementById('settings-model-panel'),
|
|
303
304
|
settingsModelList: document.getElementById('settings-model-list'),
|
|
305
|
+
settingsModelSearch: document.getElementById('settings-model-search'),
|
|
306
|
+
settingsModelHint: document.getElementById('settings-model-hint'),
|
|
304
307
|
reviewPrLink: document.getElementById('review-pr-link'),
|
|
305
308
|
reviewPrStatusLine: document.getElementById('review-pr-status-line'),
|
|
306
309
|
reviewPrStatusNotes: document.getElementById('review-pr-status-notes'),
|
|
@@ -308,6 +311,10 @@ const els = {
|
|
|
308
311
|
reviewTerminalMessage: document.getElementById('review-terminal-message'),
|
|
309
312
|
reviewRetryBtn: document.getElementById('review-retry-btn'),
|
|
310
313
|
settingsTools: document.getElementById('settings-tools'),
|
|
314
|
+
settingsMaxTurns: document.getElementById('settings-max-turns'),
|
|
315
|
+
settingsTimeout: document.getElementById('settings-timeout'),
|
|
316
|
+
settingsTimeoutMs: document.getElementById('settings-timeout-ms'),
|
|
317
|
+
settingsTestCommand: document.getElementById('settings-test-command'),
|
|
311
318
|
settingsFeedback: document.getElementById('settings-feedback'),
|
|
312
319
|
settingsSaveBtn: document.getElementById('settings-save-btn'),
|
|
313
320
|
settingsTicketSource: document.getElementById('settings-ticket-source'),
|
|
@@ -1051,6 +1058,148 @@ function sortedJobs() {
|
|
|
1051
1058
|
);
|
|
1052
1059
|
}
|
|
1053
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* @param {string} raw
|
|
1063
|
+
* @returns {string}
|
|
1064
|
+
*/
|
|
1065
|
+
function escapeHtml(raw) {
|
|
1066
|
+
return String(raw)
|
|
1067
|
+
.replaceAll('&', '&')
|
|
1068
|
+
.replaceAll('<', '<')
|
|
1069
|
+
.replaceAll('>', '>')
|
|
1070
|
+
.replaceAll('"', '"')
|
|
1071
|
+
.replaceAll("'", ''');
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* @param {string} raw
|
|
1076
|
+
* @returns {string | null}
|
|
1077
|
+
*/
|
|
1078
|
+
function safeMarkdownHref(raw) {
|
|
1079
|
+
const href = String(raw || '').trim();
|
|
1080
|
+
if (!href) return null;
|
|
1081
|
+
if (href.startsWith('#') || href.startsWith('/')) return href;
|
|
1082
|
+
try {
|
|
1083
|
+
const url = new URL(href, window.location.href);
|
|
1084
|
+
return ['http:', 'https:', 'mailto:'].includes(url.protocol) ? url.href : null;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* @param {string} raw
|
|
1092
|
+
* @returns {string}
|
|
1093
|
+
*/
|
|
1094
|
+
function renderInlineMarkdown(raw) {
|
|
1095
|
+
const stash = [];
|
|
1096
|
+
const hold = (html) => `\u0000${stash.push(html) - 1}\u0000`;
|
|
1097
|
+
let out = escapeHtml(raw);
|
|
1098
|
+
out = out.replace(/`([^`]+)`/g, (_, code) => hold(`<code>${code}</code>`));
|
|
1099
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, (_, body) => hold(`<strong>${body}</strong>`));
|
|
1100
|
+
out = out.replace(/__([^_]+)__/g, (_, body) => hold(`<strong>${body}</strong>`));
|
|
1101
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => {
|
|
1102
|
+
const safe = safeMarkdownHref(href);
|
|
1103
|
+
if (!safe) return label;
|
|
1104
|
+
return hold(
|
|
1105
|
+
`<a href="${escapeHtml(safe)}" target="_blank" rel="noopener noreferrer">${label}</a>`
|
|
1106
|
+
);
|
|
1107
|
+
});
|
|
1108
|
+
return out.replace(/\u0000(\d+)\u0000/g, (_, idx) => stash[Number(idx)] || '');
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* @param {HTMLElement | null} container
|
|
1113
|
+
* @param {string} raw
|
|
1114
|
+
*/
|
|
1115
|
+
function renderMarkdownBlock(container, raw) {
|
|
1116
|
+
if (!container) return;
|
|
1117
|
+
container.innerHTML = '';
|
|
1118
|
+
const text = String(raw || '').replace(/\r\n/g, '\n').trim();
|
|
1119
|
+
if (!text) return;
|
|
1120
|
+
|
|
1121
|
+
const lines = text.split('\n');
|
|
1122
|
+
let paragraph = [];
|
|
1123
|
+
let listEl = null;
|
|
1124
|
+
let listType = '';
|
|
1125
|
+
|
|
1126
|
+
const flushParagraph = () => {
|
|
1127
|
+
if (!paragraph.length) return;
|
|
1128
|
+
const p = document.createElement('p');
|
|
1129
|
+
p.innerHTML = renderInlineMarkdown(paragraph.join(' '));
|
|
1130
|
+
container.appendChild(p);
|
|
1131
|
+
paragraph = [];
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
const flushList = () => {
|
|
1135
|
+
if (!listEl) return;
|
|
1136
|
+
container.appendChild(listEl);
|
|
1137
|
+
listEl = null;
|
|
1138
|
+
listType = '';
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1142
|
+
const line = lines[i];
|
|
1143
|
+
if (!line.trim()) {
|
|
1144
|
+
flushParagraph();
|
|
1145
|
+
flushList();
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const fence = line.match(/^```([\w-]+)?\s*$/);
|
|
1150
|
+
if (fence) {
|
|
1151
|
+
flushParagraph();
|
|
1152
|
+
flushList();
|
|
1153
|
+
const codeLines = [];
|
|
1154
|
+
for (i += 1; i < lines.length; i += 1) {
|
|
1155
|
+
if (/^```\s*$/.test(lines[i])) break;
|
|
1156
|
+
codeLines.push(lines[i]);
|
|
1157
|
+
}
|
|
1158
|
+
const pre = document.createElement('pre');
|
|
1159
|
+
pre.className = 'markdown-code';
|
|
1160
|
+
const code = document.createElement('code');
|
|
1161
|
+
if (fence[1]) code.dataset.language = fence[1];
|
|
1162
|
+
code.textContent = codeLines.join('\n');
|
|
1163
|
+
pre.appendChild(code);
|
|
1164
|
+
container.appendChild(pre);
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
1169
|
+
if (heading) {
|
|
1170
|
+
flushParagraph();
|
|
1171
|
+
flushList();
|
|
1172
|
+
const tag = `h${heading[1].length}`;
|
|
1173
|
+
const el = document.createElement(tag);
|
|
1174
|
+
el.className = `markdown-heading markdown-heading-${heading[1].length}`;
|
|
1175
|
+
el.innerHTML = renderInlineMarkdown(heading[2]);
|
|
1176
|
+
container.appendChild(el);
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const bullet = line.match(/^\s*([-*+])\s+(.*)$/);
|
|
1181
|
+
const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
|
|
1182
|
+
if (bullet || ordered) {
|
|
1183
|
+
flushParagraph();
|
|
1184
|
+
const type = bullet ? 'ul' : 'ol';
|
|
1185
|
+
if (!listEl || listType !== type) {
|
|
1186
|
+
flushList();
|
|
1187
|
+
listEl = document.createElement(type);
|
|
1188
|
+
listType = type;
|
|
1189
|
+
}
|
|
1190
|
+
const li = document.createElement('li');
|
|
1191
|
+
li.innerHTML = renderInlineMarkdown((bullet || ordered)[2] || (bullet || ordered)[1] || '');
|
|
1192
|
+
listEl.appendChild(li);
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
paragraph.push(line.trim());
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
flushParagraph();
|
|
1200
|
+
flushList();
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1054
1203
|
/**
|
|
1055
1204
|
* @param {HTMLElement | null} container
|
|
1056
1205
|
* @param {object | null | undefined} pr
|
|
@@ -1089,8 +1238,8 @@ function renderPrStatusNotes(container, pr) {
|
|
|
1089
1238
|
|
|
1090
1239
|
if (review.body) {
|
|
1091
1240
|
const body = document.createElement('div');
|
|
1092
|
-
body.className = 'pr-status-note-body';
|
|
1093
|
-
body
|
|
1241
|
+
body.className = 'pr-status-note-body markdown-view';
|
|
1242
|
+
renderMarkdownBlock(body, review.body);
|
|
1094
1243
|
item.appendChild(body);
|
|
1095
1244
|
}
|
|
1096
1245
|
|
|
@@ -1107,6 +1256,10 @@ function renderPrStatusNotes(container, pr) {
|
|
|
1107
1256
|
container.appendChild(requested);
|
|
1108
1257
|
}
|
|
1109
1258
|
}
|
|
1259
|
+
if (typeof window !== 'undefined') {
|
|
1260
|
+
window.__acdevMarkdownRender = renderMarkdownBlock;
|
|
1261
|
+
window.__acdevMarkdownInline = renderInlineMarkdown;
|
|
1262
|
+
}
|
|
1110
1263
|
function formatTime(ts) {
|
|
1111
1264
|
try {
|
|
1112
1265
|
return new Date(ts).toLocaleTimeString([], {
|
|
@@ -1771,8 +1924,8 @@ function renderDiff(diff, opts = {}) {
|
|
|
1771
1924
|
const chip = document.createElement('div');
|
|
1772
1925
|
chip.className = 'diff-pending-chip';
|
|
1773
1926
|
const body = document.createElement('div');
|
|
1774
|
-
body.className = 'diff-pending-chip-body';
|
|
1775
|
-
body
|
|
1927
|
+
body.className = 'diff-pending-chip-body markdown-view';
|
|
1928
|
+
renderMarkdownBlock(body, c.body);
|
|
1776
1929
|
const actions = document.createElement('div');
|
|
1777
1930
|
actions.className = 'diff-pending-chip-actions';
|
|
1778
1931
|
const edit = document.createElement('button');
|
|
@@ -1922,8 +2075,7 @@ function renderDiff(diff, opts = {}) {
|
|
|
1922
2075
|
}
|
|
1923
2076
|
continue;
|
|
1924
2077
|
}
|
|
1925
|
-
|
|
1926
|
-
if (line.startsWith('@@')) {
|
|
2078
|
+
if (line.startsWith('@@ ')) {
|
|
1927
2079
|
const m = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
1928
2080
|
if (m) {
|
|
1929
2081
|
leftNo = Number(m[1]);
|
|
@@ -2065,8 +2217,8 @@ function renderPendingCommentList(jobId) {
|
|
|
2065
2217
|
}
|
|
2066
2218
|
|
|
2067
2219
|
const body = document.createElement('div');
|
|
2068
|
-
body.className = 'review-pending-item-body';
|
|
2069
|
-
body
|
|
2220
|
+
body.className = 'review-pending-item-body markdown-view';
|
|
2221
|
+
renderMarkdownBlock(body, c.body);
|
|
2070
2222
|
main.appendChild(body);
|
|
2071
2223
|
const actions = document.createElement('div');
|
|
2072
2224
|
actions.className = 'review-pending-item-actions';
|
|
@@ -4998,6 +5150,7 @@ els.addBtn.addEventListener('click', async () => {
|
|
|
4998
5150
|
|
|
4999
5151
|
const urls = splitIssueUrls(text);
|
|
5000
5152
|
const branchName = els.preferredBranch?.value?.trim() || '';
|
|
5153
|
+
const userPrompt = els.enqueueUserPrompt?.value?.trim() || '';
|
|
5001
5154
|
|
|
5002
5155
|
try {
|
|
5003
5156
|
const res = await fetch('/api/issues', {
|
|
@@ -5007,6 +5160,7 @@ els.addBtn.addEventListener('click', async () => {
|
|
|
5007
5160
|
urls,
|
|
5008
5161
|
ticketSource,
|
|
5009
5162
|
...(branchName ? { branchName } : {}),
|
|
5163
|
+
...(userPrompt ? { userPrompt } : {}),
|
|
5010
5164
|
}),
|
|
5011
5165
|
});
|
|
5012
5166
|
const data = await res.json();
|
|
@@ -5019,6 +5173,7 @@ els.addBtn.addEventListener('click', async () => {
|
|
|
5019
5173
|
}
|
|
5020
5174
|
els.issueUrls.value = '';
|
|
5021
5175
|
if (els.preferredBranch) els.preferredBranch.value = '';
|
|
5176
|
+
if (els.enqueueUserPrompt) els.enqueueUserPrompt.value = '';
|
|
5022
5177
|
const parts = [];
|
|
5023
5178
|
if (data.jobs?.length) parts.push(`Added ${data.jobs.length}`);
|
|
5024
5179
|
if (data.skipped?.length) {
|
package/public/index.html
CHANGED
|
@@ -156,6 +156,22 @@
|
|
|
156
156
|
</p>
|
|
157
157
|
</div>
|
|
158
158
|
|
|
159
|
+
<div class="enqueue-branch-field">
|
|
160
|
+
<div class="enqueue-branch-label-row">
|
|
161
|
+
<label class="field-label" for="enqueue-user-prompt">Additional instructions</label>
|
|
162
|
+
<span class="enqueue-branch-optional">Optional</span>
|
|
163
|
+
</div>
|
|
164
|
+
<textarea
|
|
165
|
+
id="enqueue-user-prompt"
|
|
166
|
+
class="input textarea enqueue-textarea"
|
|
167
|
+
rows="3"
|
|
168
|
+
placeholder="Anything specific you want the agent to know or do…"
|
|
169
|
+
></textarea>
|
|
170
|
+
<p class="enqueue-input-hint">
|
|
171
|
+
Appended to the agent's instructions for these tickets. Leave empty to use the issue content only.
|
|
172
|
+
</p>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
159
175
|
<div class="enqueue-info-banner" id="enqueue-info-banner" role="note">
|
|
160
176
|
<svg class="enqueue-info-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
161
177
|
<circle cx="12" cy="12" r="10"/>
|
package/public/styles.css
CHANGED
|
@@ -1620,15 +1620,67 @@ a { color: var(--primary); text-underline-offset: 3px; }
|
|
|
1620
1620
|
background: var(--surface-2);
|
|
1621
1621
|
}
|
|
1622
1622
|
|
|
1623
|
-
.
|
|
1623
|
+
.markdown-view {
|
|
1624
|
+
line-height: 1.45;
|
|
1625
|
+
word-break: break-word;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.markdown-view > :first-child {
|
|
1629
|
+
margin-top: 0;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.markdown-view > :last-child {
|
|
1633
|
+
margin-bottom: 0;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.markdown-view p,
|
|
1637
|
+
.markdown-view ul,
|
|
1638
|
+
.markdown-view ol,
|
|
1639
|
+
.markdown-view pre,
|
|
1640
|
+
.markdown-view blockquote,
|
|
1641
|
+
.markdown-view h1,
|
|
1642
|
+
.markdown-view h2,
|
|
1643
|
+
.markdown-view h3,
|
|
1644
|
+
.markdown-view h4,
|
|
1645
|
+
.markdown-view h5,
|
|
1646
|
+
.markdown-view h6 {
|
|
1647
|
+
margin: 0 0 8px;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.markdown-view ul,
|
|
1651
|
+
.markdown-view ol {
|
|
1652
|
+
padding-left: 20px;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
.markdown-view code {
|
|
1656
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
1624
1657
|
font-size: 12px;
|
|
1625
|
-
|
|
1626
|
-
|
|
1658
|
+
padding: 1px 4px;
|
|
1659
|
+
background: var(--bg);
|
|
1660
|
+
border-radius: 4px;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
.markdown-view pre {
|
|
1664
|
+
margin: 0 0 8px;
|
|
1665
|
+
padding: 8px 10px;
|
|
1666
|
+
background: var(--bg);
|
|
1667
|
+
border: 1px solid var(--border-soft);
|
|
1668
|
+
border-radius: 8px;
|
|
1669
|
+
overflow-x: auto;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
.markdown-view pre code {
|
|
1673
|
+
padding: 0;
|
|
1674
|
+
background: transparent;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.markdown-view a {
|
|
1678
|
+
color: var(--primary);
|
|
1679
|
+
text-decoration: underline;
|
|
1627
1680
|
}
|
|
1628
1681
|
|
|
1629
1682
|
.pr-status-note-body {
|
|
1630
|
-
|
|
1631
|
-
line-height: 1.45;
|
|
1683
|
+
font-size: 13px;
|
|
1632
1684
|
}
|
|
1633
1685
|
|
|
1634
1686
|
/* —— Review file selection —— */
|
|
@@ -1943,7 +1995,7 @@ body.diff-fs-open {
|
|
|
1943
1995
|
|
|
1944
1996
|
.diff-pending-chip-body {
|
|
1945
1997
|
color: var(--text);
|
|
1946
|
-
|
|
1998
|
+
font-size: 12px;
|
|
1947
1999
|
}
|
|
1948
2000
|
|
|
1949
2001
|
.diff-pending-chip-actions {
|
|
@@ -1953,6 +2005,10 @@ body.diff-fs-open {
|
|
|
1953
2005
|
flex-wrap: wrap;
|
|
1954
2006
|
}
|
|
1955
2007
|
|
|
2008
|
+
.review-pending-item-body {
|
|
2009
|
+
font-size: 13px;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1956
2012
|
.review-pending-list {
|
|
1957
2013
|
display: flex;
|
|
1958
2014
|
flex-direction: column;
|
package/src/agent.js
CHANGED
|
@@ -89,6 +89,7 @@ export function stripAiAttribution(text) {
|
|
|
89
89
|
* comments?: Array<{ author: string, body: string }>,
|
|
90
90
|
* },
|
|
91
91
|
* jiraPrLinkPhrase?: string,
|
|
92
|
+
* userPrompt?: string,
|
|
92
93
|
* }} [context]
|
|
93
94
|
*/
|
|
94
95
|
export function buildPrompt(issueUrl, config, context = {}) {
|
|
@@ -163,6 +164,14 @@ export function buildPrompt(issueUrl, config, context = {}) {
|
|
|
163
164
|
);
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
if (context.userPrompt && context.userPrompt.trim()) {
|
|
168
|
+
parts.push(
|
|
169
|
+
'',
|
|
170
|
+
'Additional instructions from the user (optional — follow them unless they conflict with the rules above):',
|
|
171
|
+
context.userPrompt.trim()
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
166
175
|
// Drop empty strings from optional browse URL line
|
|
167
176
|
const cleaned = parts.filter((p) => p !== '');
|
|
168
177
|
|
|
@@ -579,6 +588,7 @@ function stubAgentResult(onEvent, title, body) {
|
|
|
579
588
|
* ticketSource?: 'github' | 'jira',
|
|
580
589
|
* jiraKey?: string,
|
|
581
590
|
* jiraIssue?: object,
|
|
591
|
+
* userPrompt?: string,
|
|
582
592
|
* queryFn?: typeof query,
|
|
583
593
|
* callModelFn?: (args: object) => object,
|
|
584
594
|
* }} params
|
|
@@ -594,6 +604,7 @@ export async function runAgentOnIssue({
|
|
|
594
604
|
ticketSource,
|
|
595
605
|
jiraKey,
|
|
596
606
|
jiraIssue,
|
|
607
|
+
userPrompt,
|
|
597
608
|
queryFn = query,
|
|
598
609
|
callModelFn,
|
|
599
610
|
}) {
|
|
@@ -613,6 +624,7 @@ export async function runAgentOnIssue({
|
|
|
613
624
|
jiraKey,
|
|
614
625
|
jiraIssue,
|
|
615
626
|
jiraPrLinkPhrase: config.jiraPrLinkPhrase,
|
|
627
|
+
userPrompt,
|
|
616
628
|
});
|
|
617
629
|
|
|
618
630
|
const { resultText, meta, usage } = await runConfiguredQuery({
|
package/src/server.js
CHANGED
|
@@ -68,6 +68,9 @@ const CLEARABLE_STATUSES = new Set([
|
|
|
68
68
|
'failed',
|
|
69
69
|
]);
|
|
70
70
|
|
|
71
|
+
/** Max length for optional user-supplied prompt appended to the agent instructions. */
|
|
72
|
+
const USER_PROMPT_MAX_LEN = 4000;
|
|
73
|
+
|
|
71
74
|
/**
|
|
72
75
|
* Worktree directory id: Jira key or GitHub issue number.
|
|
73
76
|
* @param {{ jiraKey?: string, issueNumber?: number }} job
|
|
@@ -471,6 +474,7 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
471
474
|
ticketSource: isJira ? 'jira' : 'github',
|
|
472
475
|
jiraKey: job.jiraKey,
|
|
473
476
|
jiraIssue,
|
|
477
|
+
userPrompt: job.userPrompt,
|
|
474
478
|
});
|
|
475
479
|
|
|
476
480
|
if (!store.getJob(jobId)) return;
|
|
@@ -833,6 +837,17 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
833
837
|
}
|
|
834
838
|
const preferredBranchName = preferredParsed.value;
|
|
835
839
|
|
|
840
|
+
const rawUserPrompt = req.body?.userPrompt;
|
|
841
|
+
if (rawUserPrompt != null && typeof rawUserPrompt !== 'string') {
|
|
842
|
+
return res.status(400).json({ error: 'userPrompt must be a string' });
|
|
843
|
+
}
|
|
844
|
+
const userPrompt = typeof rawUserPrompt === 'string' ? rawUserPrompt.trim() : '';
|
|
845
|
+
if (userPrompt.length > USER_PROMPT_MAX_LEN) {
|
|
846
|
+
return res.status(400).json({
|
|
847
|
+
error: `userPrompt must be ${USER_PROMPT_MAX_LEN} characters or fewer.`,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
836
851
|
const ticketSource =
|
|
837
852
|
req.body?.ticketSource === 'jira' || req.body?.ticketSource === 'github'
|
|
838
853
|
? req.body.ticketSource
|
|
@@ -929,6 +944,7 @@ export function createServer({ repoRoot, config, store, useStubAgent = false, de
|
|
|
929
944
|
ticketSource: item.ticketSource,
|
|
930
945
|
jiraKey: item.jiraKey,
|
|
931
946
|
...(preferredBranchName ? { preferredBranchName } : {}),
|
|
947
|
+
...(userPrompt ? { userPrompt } : {}),
|
|
932
948
|
llmProvider: llm.llmProvider,
|
|
933
949
|
model: llm.model,
|
|
934
950
|
});
|
package/src/store.js
CHANGED
|
@@ -53,19 +53,21 @@ export class Store {
|
|
|
53
53
|
* @param {{
|
|
54
54
|
* issueUrl: string,
|
|
55
55
|
* issueNumber?: number,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
* ticketSource?: 'github' | 'jira',
|
|
57
|
+
* jiraKey?: string,
|
|
58
|
+
* preferredBranchName?: string,
|
|
59
|
+
* llmProvider?: 'claude' | 'openrouter',
|
|
60
|
+
* model?: string,
|
|
61
|
+
* userPrompt?: string,
|
|
62
|
+
* }} data
|
|
63
|
+
* @returns {Job}
|
|
64
|
+
*/
|
|
64
65
|
addJob(data) {
|
|
65
66
|
const now = new Date().toISOString();
|
|
66
67
|
const ticketSource = data.ticketSource === 'jira' ? 'jira' : 'github';
|
|
67
68
|
const llmProvider = data.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
|
|
68
69
|
const model = typeof data.model === 'string' ? data.model.trim() : '';
|
|
70
|
+
const userPrompt = typeof data.userPrompt === 'string' ? data.userPrompt.trim() : '';
|
|
69
71
|
/** @type {Job} */
|
|
70
72
|
const job = {
|
|
71
73
|
id: randomUUID(),
|
|
@@ -76,6 +78,7 @@ export class Store {
|
|
|
76
78
|
...(model ? { model } : {}),
|
|
77
79
|
...(data.jiraKey ? { jiraKey: data.jiraKey } : {}),
|
|
78
80
|
...(data.preferredBranchName ? { preferredBranchName: data.preferredBranchName } : {}),
|
|
81
|
+
...(userPrompt ? { userPrompt } : {}),
|
|
79
82
|
status: 'queued',
|
|
80
83
|
createdAt: now,
|
|
81
84
|
updatedAt: now,
|