acdev 1.0.16 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acdev",
3
- "version": "1.0.16",
3
+ "version": "1.1.0",
4
4
  "description": "Local CLI + web UI for running AI agents on GitHub issues via git worktrees",
5
5
  "type": "module",
6
6
  "bin": {
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'),
@@ -5143,6 +5150,7 @@ els.addBtn.addEventListener('click', async () => {
5143
5150
 
5144
5151
  const urls = splitIssueUrls(text);
5145
5152
  const branchName = els.preferredBranch?.value?.trim() || '';
5153
+ const userPrompt = els.enqueueUserPrompt?.value?.trim() || '';
5146
5154
 
5147
5155
  try {
5148
5156
  const res = await fetch('/api/issues', {
@@ -5152,6 +5160,7 @@ els.addBtn.addEventListener('click', async () => {
5152
5160
  urls,
5153
5161
  ticketSource,
5154
5162
  ...(branchName ? { branchName } : {}),
5163
+ ...(userPrompt ? { userPrompt } : {}),
5155
5164
  }),
5156
5165
  });
5157
5166
  const data = await res.json();
@@ -5164,6 +5173,7 @@ els.addBtn.addEventListener('click', async () => {
5164
5173
  }
5165
5174
  els.issueUrls.value = '';
5166
5175
  if (els.preferredBranch) els.preferredBranch.value = '';
5176
+ if (els.enqueueUserPrompt) els.enqueueUserPrompt.value = '';
5167
5177
  const parts = [];
5168
5178
  if (data.jobs?.length) parts.push(`Added ${data.jobs.length}`);
5169
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/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
- * ticketSource?: 'github' | 'jira',
57
- * jiraKey?: string,
58
- * preferredBranchName?: string,
59
- * llmProvider?: 'claude' | 'openrouter',
60
- * model?: string,
61
- * }} data
62
- * @returns {Job}
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,