specrails-desktop 2.59.2 → 2.60.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/client/dist/assets/{ActivityFeedPage-vAY9C71W.js → ActivityFeedPage-3J7fR8yB.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-CpwjGXQm.js → AgentBrowserCapture-D4a3wYM1.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-BHeoJVxQ.js → AgentModeAnalyticsPane-Dps30tk9.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-B0Nf3YkN.js → AgentModeCodePane-CvLimey8.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CpbIItY_.js → AgentModeJobsPane-25tQlLpF.js} +1 -1
- package/client/dist/assets/{AgentsPage-BVJAFXn3.js → AgentsPage-CUDp_sfw.js} +2 -2
- package/client/dist/assets/{AnalyticsPage-DDWees-K.js → AnalyticsPage-DXWMGn8b.js} +1 -1
- package/client/dist/assets/{CodePage-CzV5tkvS.js → CodePage-D981zRHy.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DfRHNK7i.js → DesktopAnalyticsPage-BkFBq0E6.js} +1 -1
- package/client/dist/assets/{DocsDialog-BxduBYbh.js → DocsDialog-BL7uSbIJ.js} +1 -1
- package/client/dist/assets/{DocsPage-DOVeRJn_.js → DocsPage-CsDLbmZX.js} +1 -1
- package/client/dist/assets/{ExportDropdown-BnKVV8UK.js → ExportDropdown-B4Olg1Fk.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-Da9dvM-0.js → InteractiveJobComposer-Z6fl4791.js} +2 -2
- package/client/dist/assets/JobDetailModal-DS_Owxs6.js +1 -0
- package/client/dist/assets/JobDetailPage-1TUQ9vlE.js +1 -0
- package/client/dist/assets/{JobsPage-0vdlbNik.js → JobsPage-BSrl8P7u.js} +1 -1
- package/client/dist/assets/LoopBuilderPage-Dh5uk2Qy.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-Df-S8NGf.js → LoopPreviewModal-CH1r-KhU.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-sdb7QbSo.js → LoopWindowSurface-CiZdI6aw.js} +1 -1
- package/client/dist/assets/{LoopsPage-CDfCC9Px.js → LoopsPage-BwFujdGi.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-DKlPf54Y.js → MinimizedChatsContext-BD2bsRBe.js} +1 -1
- package/client/dist/assets/PluginsPage-B8f49Mvn.js +2 -0
- package/client/dist/assets/{ProjectSettingsDialog-DOkjT7xv.js → ProjectSettingsDialog-D7q-O6h3.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-CJVpOUpZ.js → RepositoryDeliveries-NNorcGzT.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-BeM-2gIZ.js → RepositoryScopeSelector-D-jb4bI_.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-EBCim2w_.js → ReviewPacketPage-DpZSzF-Q.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-1-92UGwN.js → TemplatePreviewModal-5CZdAZ0G.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-CkVXuHxU.js → TicketDetailModalContext-BHmtj1As.js} +1 -1
- package/client/dist/assets/{Trans-CzhPjnjB.js → Trans-wmTUDVyC.js} +1 -1
- package/client/dist/assets/{agentRuntime-TXPQowKJ.js → agentRuntime-7Ydcvr6P.js} +1 -1
- package/client/dist/assets/{agentRuntime-Bpx3yWHK.js → agentRuntime-BHVw9orA.js} +1 -1
- package/client/dist/assets/{agentRuntime-15Pkk6we.js → agentRuntime-DCPDaQcV.js} +1 -1
- package/client/dist/assets/{agentRuntime-C78n13To.js → agentRuntime-DKTw4Ajg.js} +1 -1
- package/client/dist/assets/{agentRuntime-CCI7XF4c.js → agentRuntime-DVhij_i_.js} +1 -1
- package/client/dist/assets/{agentRuntime-Ddqvp2bP.js → agentRuntime-Doss-T8o.js} +1 -1
- package/client/dist/assets/{agentRuntime-CKDHe2-a.js → agentRuntime-eZSMUlkM.js} +1 -1
- package/client/dist/assets/{agentRuntime-hqZRy08E.js → agentRuntime-qSSU7fQk.js} +1 -1
- package/client/dist/assets/agents-B6ZsnARo.js +1 -0
- package/client/dist/assets/agents-BORdhe0K.js +1 -0
- package/client/dist/assets/agents-C6aq9Oqs.js +1 -0
- package/client/dist/assets/agents-Cbsc4bcX.js +1 -0
- package/client/dist/assets/agents-D0uYR6hq.js +1 -0
- package/client/dist/assets/agents-Doe1sHY5.js +1 -0
- package/client/dist/assets/agents-DyNi_I9m.js +1 -0
- package/client/dist/assets/agents-StKxiTg0.js +1 -0
- package/client/dist/assets/agentstudio-7cYCXn-L.js +1 -0
- package/client/dist/assets/{agentstudio-QdAJIDef.js → agentstudio-BHWQh8G3.js} +1 -1
- package/client/dist/assets/agentstudio-BjnWHyRs.js +1 -0
- package/client/dist/assets/{agentstudio-fLXpRGIi.js → agentstudio-BkV8vcK4.js} +1 -1
- package/client/dist/assets/{agentstudio-BWAKeGgB.js → agentstudio-BzugqB_N.js} +1 -1
- package/client/dist/assets/{agentstudio-Cja8Sjcp.js → agentstudio-CWbp7KvH.js} +1 -1
- package/client/dist/assets/agentstudio-DG6plf5o.js +1 -0
- package/client/dist/assets/agentstudio-DkZVF-Cc.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-DlQaXc7c.js → formatDistanceToNow-Dax4zqPF.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxUFGcVq.js → getTimezoneOffsetInMilliseconds-dpIo42JZ.js} +1 -1
- package/client/dist/assets/{index-Czov72g7.js → index-g8S62tpS.js} +7 -7
- package/client/dist/assets/jira-B6OYw4CJ.js +1 -0
- package/client/dist/assets/jira-BTF6RaOe.js +1 -0
- package/client/dist/assets/jira-CEetIB2q.js +1 -0
- package/client/dist/assets/jira-CIzsZLw5.js +1 -0
- package/client/dist/assets/{jira-BOoaUXbb.js → jira-Cq6yvN4q.js} +1 -1
- package/client/dist/assets/jira-Daw9tmpI.js +1 -0
- package/client/dist/assets/jira-FXr7u2lh.js +1 -0
- package/client/dist/assets/jira-LaNQn7yl.js +1 -0
- package/client/dist/assets/{jira-api-CbJvbRYx.js → jira-api-CLrIvXFm.js} +1 -1
- package/client/dist/assets/{jobs-DZAbh1m2.js → jobs-ARwD85B9.js} +1 -1
- package/client/dist/assets/{jobs-BD47Ov11.js → jobs-BMF1w4WT.js} +1 -1
- package/client/dist/assets/{jobs-tWjcDF_6.js → jobs-CBfzGLwm.js} +1 -1
- package/client/dist/assets/{jobs-CSrRx5lD.js → jobs-CPjiluBY.js} +1 -1
- package/client/dist/assets/{jobs-epP7gt8W.js → jobs-CQQd58pY.js} +1 -1
- package/client/dist/assets/{jobs-1G3wodhl.js → jobs-De3EqaO0.js} +1 -1
- package/client/dist/assets/{jobs-BLOoVRRl.js → jobs-Du9-Ei9a.js} +1 -1
- package/client/dist/assets/{jobs-B4pu8TdF.js → jobs-qMHbnxwC.js} +1 -1
- package/client/dist/assets/{project-repositories-BkBD3rxx.js → project-repositories-BkyT8AjU.js} +1 -1
- package/client/dist/assets/{spending-CYotH-9N.js → spending-Oov7p52A.js} +1 -1
- package/client/dist/assets/{useDesktop-DpxVGfUC.js → useDesktop-DpiS8Uov.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-CfGRbhr9.js → useRuntimeRuns-DBm1mul0.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-C17TGqT8.js → useSharedWebSocket-BZxKaVNT.js} +2 -2
- package/client/dist/index.html +19 -19
- package/docs/getting-started.md +2 -2
- package/docs/guide/de/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/de/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/de/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/en/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/en/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/en/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/es/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/es/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/es/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/fr/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/fr/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/it/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/it/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/it/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/ja/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/ja/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/pt/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/pt/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/zh/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/zh/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +3 -3
- package/docs/internals/adding-a-provider.md +7 -6
- package/docs/internals/configuration.md +45 -0
- package/docs/internals/core-runtime-updates.md +19 -0
- package/docs/internals/loop-step-log-explorer.md +27 -0
- package/docs/internals/mission-rail-cards.md +7 -0
- package/docs/internals/profiles.md +17 -10
- package/docs/internals/review-packet.md +1 -1
- package/docs/internals/safe-pr-review-flow.md +9 -0
- package/docs/internals/source-map.md +6 -2
- package/docs/jira-integration-plan.md +7 -4
- package/docs/running-pipelines.md +2 -2
- package/package.json +1 -1
- package/server/dist/core-compat.js +7 -3
- package/server/dist/core-execution.js +5 -18
- package/server/dist/jira/jira-db.js +33 -6
- package/server/dist/jira/jira-materializer.js +11 -9
- package/server/dist/jira/jira-status-resolver.js +64 -22
- package/server/dist/jira/jira-sync-manager.js +40 -1
- package/server/dist/jira-router.js +8 -41
- package/server/dist/mcp/guide.js +4 -3
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +22 -70
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +7 -4
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +24 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-log.js +252 -0
- package/server/dist/modules/agents/runtime/profiles-router.js +74 -36
- package/server/dist/modules/delivery/runtime/rail-relaunch.js +10 -2
- package/server/dist/modules/execution/runtime/queue-manager.js +7 -9
- package/server/dist/modules/loops/runtime/loop-agents.js +2 -5
- package/server/dist/modules/loops/runtime/loop-command-catalog.js +5 -5
- package/server/dist/modules/loops/runtime/loop-definition.js +1 -1
- package/server/dist/modules/loops/runtime/loop-executors.js +0 -2
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +5 -4
- package/server/dist/modules/loops/runtime/loop-run-manager.js +5 -3
- package/server/dist/modules/loops/runtime/loops-router.js +3 -1
- package/server/dist/path-resolver.js +41 -20
- package/server/dist/project-env.js +22 -6
- package/server/dist/providers/gemini-agent-ack.js +13 -10
- package/server/dist/setup-manager.js +28 -43
- package/server/dist/util/checkout-path.js +22 -0
- package/server/dist/workspace-manager.js +13 -71
- package/server/dist/worktree-node-modules.js +70 -34
- package/client/dist/assets/JobDetailModal-DhwKbZGe.js +0 -1
- package/client/dist/assets/JobDetailPage-MtPE-E_1.js +0 -1
- package/client/dist/assets/LoopBuilderPage-DCUHozXh.js +0 -1
- package/client/dist/assets/PluginsPage-iL0G3rQQ.js +0 -2
- package/client/dist/assets/agents-CA5eT1d0.js +0 -1
- package/client/dist/assets/agents-CAgy-5uS.js +0 -1
- package/client/dist/assets/agents-CNm6Qzlv.js +0 -1
- package/client/dist/assets/agents-CTwbx-fb.js +0 -1
- package/client/dist/assets/agents-DEGV1Vao.js +0 -1
- package/client/dist/assets/agents-HsDF1aQU.js +0 -1
- package/client/dist/assets/agents-IQjdA-z9.js +0 -1
- package/client/dist/assets/agents-RkGoteXh.js +0 -1
- package/client/dist/assets/agentstudio-B2CTtQgZ.js +0 -1
- package/client/dist/assets/agentstudio-DtfPcr99.js +0 -1
- package/client/dist/assets/agentstudio-X70BKf6u.js +0 -1
- package/client/dist/assets/agentstudio-qXKqVnWS.js +0 -1
- package/client/dist/assets/jira-BNZu8f9O.js +0 -1
- package/client/dist/assets/jira-BYVN4L2u.js +0 -1
- package/client/dist/assets/jira-CKJg3HZd.js +0 -1
- package/client/dist/assets/jira-DDDsZ4ub.js +0 -1
- package/client/dist/assets/jira-DXwYjVE4.js +0 -1
- package/client/dist/assets/jira-Dn6U2nRW.js +0 -1
- package/client/dist/assets/jira-j7hVolR5.js +0 -1
- package/server/dist/modules/loops/runtime/loop-agent-defaults.json +0 -6
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// 2. Category fallback anchored on `statusCategory.key` (never the localizable
|
|
10
10
|
// status NAME), with a cancel/ship lexicon to disambiguate the `done`
|
|
11
11
|
// category (e.g. `Won't Do` vs `Released`).
|
|
12
|
-
// 3.
|
|
13
|
-
//
|
|
12
|
+
// 3. Bounded category-monotonic transition walk where there is no direct edge
|
|
13
|
+
// to the target category. Unknown lateral paths are never explored by writes.
|
|
14
14
|
//
|
|
15
15
|
// This module is PURE: the walker takes async callbacks (getTransitions /
|
|
16
16
|
// applyTransition) so it is fully testable without HTTP.
|
|
@@ -60,12 +60,14 @@ function nameMatches(name, lexicon) {
|
|
|
60
60
|
function isCancelName(name) {
|
|
61
61
|
return nameMatches(name, CANCEL_LEXICON);
|
|
62
62
|
}
|
|
63
|
+
function matchesStatusTarget(id, name, target) {
|
|
64
|
+
return id === target || (name !== undefined && name.toLowerCase() === target.toLowerCase());
|
|
65
|
+
}
|
|
63
66
|
/** Does this transition's destination match the user's explicitly configured
|
|
64
|
-
* target (by status id
|
|
67
|
+
* target (by status id or status name)? Case-insensitive on name.
|
|
65
68
|
* The explicit config ALWAYS wins over the cancel/ship lexicon heuristics. */
|
|
66
69
|
function matchesExplicit(t, explicitTarget) {
|
|
67
|
-
|
|
68
|
-
return t.to.id === explicitTarget || t.to.name.toLowerCase() === e || t.id === explicitTarget;
|
|
70
|
+
return matchesStatusTarget(t.to.id, t.to.name, explicitTarget);
|
|
69
71
|
}
|
|
70
72
|
function transitionCategory(t) {
|
|
71
73
|
const k = t.to.statusCategory?.key;
|
|
@@ -81,6 +83,8 @@ function transitionCategory(t) {
|
|
|
81
83
|
*/
|
|
82
84
|
function pickDirectTransition(transitions, state, explicitTarget) {
|
|
83
85
|
if (explicitTarget) {
|
|
86
|
+
// Values identify destination statuses, never transition IDs: the two ID
|
|
87
|
+
// namespaces can collide, including with an unrelated terminal transition.
|
|
84
88
|
const t = transitions.find((tr) => matchesExplicit(tr, explicitTarget));
|
|
85
89
|
if (t)
|
|
86
90
|
return t;
|
|
@@ -117,9 +121,9 @@ function pickDirectTransition(transitions, state, explicitTarget) {
|
|
|
117
121
|
}
|
|
118
122
|
/**
|
|
119
123
|
* Pick a transition that moves the issue closer to the target category (used by
|
|
120
|
-
* the
|
|
124
|
+
* the bounded walk when no direct transition exists). Returns the edge whose target
|
|
121
125
|
* category is strictly closer (in rank distance) to the goal, never overshooting
|
|
122
|
-
* past it, preferring the
|
|
126
|
+
* past it, preferring the furthest progress in either direction.
|
|
123
127
|
*/
|
|
124
128
|
function pickProgressTransition(transitions, currentCategory, targetCategory, visitedStatusIds, opts) {
|
|
125
129
|
const goal = categoryRank(targetCategory);
|
|
@@ -189,26 +193,57 @@ function buildTransitionFields(transition, state) {
|
|
|
189
193
|
}
|
|
190
194
|
return Object.keys(out).length > 0 ? { fields: out } : {};
|
|
191
195
|
}
|
|
196
|
+
function diagnosticValue(value, limit = 64) {
|
|
197
|
+
const compact = value.replace(/[\u0000-\u001f\u007f]/g, ' ').trim();
|
|
198
|
+
if (compact.length <= limit && JSON.stringify(compact).length <= limit)
|
|
199
|
+
return JSON.stringify(compact);
|
|
200
|
+
let preview = compact.slice(0, limit);
|
|
201
|
+
while (JSON.stringify(`${preview}…`).length > limit)
|
|
202
|
+
preview = preview.slice(0, -1);
|
|
203
|
+
return JSON.stringify(`${preview}…`);
|
|
204
|
+
}
|
|
205
|
+
function statusDiagnostic(name, id) {
|
|
206
|
+
return `${name ? diagnosticValue(name, 48) : '(unknown)'}${id ? ` (id ${diagnosticValue(id, 24)})` : ''}`;
|
|
207
|
+
}
|
|
208
|
+
function noPathReason(summary, currentName, currentId, explicitTarget, transitions) {
|
|
209
|
+
// Dead-letter storage retains at most 500 characters. Budget the entire
|
|
210
|
+
// message here so available destinations are never silently cut off there.
|
|
211
|
+
const prefix = `${summary.slice(0, 160)}; current status ${statusDiagnostic(currentName, currentId)}`
|
|
212
|
+
+ (explicitTarget ? `; configured target ${diagnosticValue(explicitTarget)}` : '')
|
|
213
|
+
+ '; available destinations: ';
|
|
214
|
+
if (transitions === undefined)
|
|
215
|
+
return `${prefix}not fetched for current status`;
|
|
216
|
+
const available = [];
|
|
217
|
+
for (const transition of transitions.slice(0, 8)) {
|
|
218
|
+
const candidate = [...available, statusDiagnostic(transition.to.name, transition.to.id)];
|
|
219
|
+
const omitted = transitions.length - candidate.length;
|
|
220
|
+
const suffix = omitted > 0 ? `; +${omitted} more` : '';
|
|
221
|
+
if (`${prefix}${candidate.join(', ')}${suffix}`.length > 500)
|
|
222
|
+
break;
|
|
223
|
+
available.push(candidate[candidate.length - 1]);
|
|
224
|
+
}
|
|
225
|
+
const omitted = transitions.length - available.length;
|
|
226
|
+
return `${prefix}${available.join(', ') || (omitted ? '(omitted)' : 'none')}${omitted > 0 ? `; +${omitted} more` : ''}`;
|
|
227
|
+
}
|
|
192
228
|
/**
|
|
193
229
|
* Walk the transition graph from the current category to the target category for
|
|
194
230
|
* `state`, applying edges per hop (you can only see the current status's
|
|
195
|
-
* outgoing edges).
|
|
196
|
-
*
|
|
197
|
-
* sitting on it (see `currentStatusName`).
|
|
231
|
+
* outgoing edges). An explicit destination's identity wins over the default
|
|
232
|
+
* category, including idempotency. Without a destination, the category suffices.
|
|
198
233
|
*/
|
|
199
234
|
async function walkToCategory(args) {
|
|
200
235
|
const target = targetCategoryFor(args.state);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return { status: 'noop' };
|
|
207
|
-
}
|
|
236
|
+
const alreadyAtTarget = args.explicitTarget
|
|
237
|
+
? matchesStatusTarget(args.currentStatusId, args.currentStatusName, args.explicitTarget)
|
|
238
|
+
: args.currentCategory === target;
|
|
239
|
+
if (alreadyAtTarget)
|
|
240
|
+
return { status: 'noop' };
|
|
208
241
|
const maxHops = args.maxHops ?? 5;
|
|
209
|
-
const visited = new Set();
|
|
242
|
+
const visited = new Set(args.currentStatusId ? [args.currentStatusId] : []);
|
|
210
243
|
const applied = [];
|
|
211
244
|
let currentCategory = args.currentCategory;
|
|
245
|
+
let currentStatusName = args.currentStatusName;
|
|
246
|
+
let currentStatusId = args.currentStatusId;
|
|
212
247
|
for (let hop = 0; hop < maxHops; hop++) {
|
|
213
248
|
let transitions;
|
|
214
249
|
try {
|
|
@@ -230,7 +265,7 @@ async function walkToCategory(args) {
|
|
|
230
265
|
return { status: 'error', reason: err instanceof Error ? err.message : String(err) };
|
|
231
266
|
}
|
|
232
267
|
applied.push(direct.id);
|
|
233
|
-
return { status: 'applied', finalCategory:
|
|
268
|
+
return { status: 'applied', finalCategory: transitionCategory(direct), transitions: applied };
|
|
234
269
|
}
|
|
235
270
|
// No direct edge → step toward the target category. For any non-cancel walk
|
|
236
271
|
// the step must avoid reject statuses so the issue never overshoots onto a
|
|
@@ -242,7 +277,7 @@ async function walkToCategory(args) {
|
|
|
242
277
|
if (!step) {
|
|
243
278
|
return {
|
|
244
279
|
status: 'no_path',
|
|
245
|
-
reason: `no workflow transition from category "${currentCategory}" toward "${target}"`,
|
|
280
|
+
reason: noPathReason(`no workflow transition from category "${currentCategory}" toward "${target}"`, currentStatusName, currentStatusId, args.explicitTarget, transitions),
|
|
246
281
|
};
|
|
247
282
|
}
|
|
248
283
|
const plan = buildTransitionFields(step, args.state);
|
|
@@ -256,12 +291,19 @@ async function walkToCategory(args) {
|
|
|
256
291
|
}
|
|
257
292
|
applied.push(step.id);
|
|
258
293
|
visited.add(step.to.id);
|
|
294
|
+
currentStatusName = step.to.name;
|
|
295
|
+
currentStatusId = step.to.id;
|
|
259
296
|
const stepCat = transitionCategory(step);
|
|
260
297
|
if (stepCat)
|
|
261
298
|
currentCategory = stepCat;
|
|
262
|
-
if (currentCategory === target) {
|
|
299
|
+
if (!args.explicitTarget && currentCategory === target) {
|
|
263
300
|
return { status: 'applied', finalCategory: target, transitions: applied };
|
|
264
301
|
}
|
|
265
302
|
}
|
|
266
|
-
return {
|
|
303
|
+
return {
|
|
304
|
+
status: 'no_path',
|
|
305
|
+
reason: noPathReason(args.explicitTarget
|
|
306
|
+
? `configured status not reached within ${maxHops} hops`
|
|
307
|
+
: `target category "${target}" not reached within ${maxHops} hops`, currentStatusName, currentStatusId, args.explicitTarget, undefined),
|
|
308
|
+
};
|
|
267
309
|
}
|
|
@@ -999,6 +999,11 @@ class JiraSyncManager {
|
|
|
999
999
|
return;
|
|
1000
1000
|
let changed = false;
|
|
1001
1001
|
while (!this.authPaused) {
|
|
1002
|
+
// A failed transition may have acquired a newer intention while its HTTP
|
|
1003
|
+
// request was in flight. Reconcile before each claim, including legacy
|
|
1004
|
+
// manual retries whose newer transition has already completed.
|
|
1005
|
+
if ((0, jira_db_1.reconcileSupersededTransitions)(this.db) > 0)
|
|
1006
|
+
changed = true;
|
|
1002
1007
|
const batch = (0, jira_db_1.claimDrainable)(this.db, MAX_DRAIN_BATCH);
|
|
1003
1008
|
if (batch.length === 0)
|
|
1004
1009
|
break;
|
|
@@ -1099,6 +1104,7 @@ class JiraSyncManager {
|
|
|
1099
1104
|
// the SAME category (e.g. In Progress → In Review, both indeterminate)
|
|
1100
1105
|
// while still noop-ing when the issue already sits on the target.
|
|
1101
1106
|
currentStatusName: issue.data.fields.status?.name,
|
|
1107
|
+
currentStatusId: issue.data.fields.status?.id,
|
|
1102
1108
|
explicitTarget,
|
|
1103
1109
|
getTransitions: async () => {
|
|
1104
1110
|
const res = await client.getTransitions(payload.jiraIssueId);
|
|
@@ -1231,6 +1237,7 @@ class JiraSyncManager {
|
|
|
1231
1237
|
}
|
|
1232
1238
|
}
|
|
1233
1239
|
broadcastOutboxState() {
|
|
1240
|
+
(0, jira_db_1.reconcileSupersededTransitions)(this.db);
|
|
1234
1241
|
const counts = (0, jira_db_1.countOutboxByState)(this.db);
|
|
1235
1242
|
this.broadcast({ type: 'jira.outbox_changed', projectId: this.projectId, pending: counts.pending + counts.inflight, dead: counts.dead });
|
|
1236
1243
|
}
|
|
@@ -1309,11 +1316,43 @@ class JiraSyncManager {
|
|
|
1309
1316
|
return (0, jira_db_1.listLinks)(this.db);
|
|
1310
1317
|
}
|
|
1311
1318
|
listOutbox(state) {
|
|
1312
|
-
|
|
1319
|
+
if ((0, jira_db_1.reconcileSupersededTransitions)(this.db) > 0)
|
|
1320
|
+
this.broadcastOutboxState();
|
|
1321
|
+
const links = new Map((0, jira_db_1.listLinks)(this.db).map((link) => [link.jiraIssueId, link.jiraKey]));
|
|
1322
|
+
const statusMap = (0, jira_db_1.getConnection)(this.db, this.projectId)?.statusMap;
|
|
1323
|
+
const logicalStates = ['todo', 'in_progress', 'on_review', 'done', 'cancelled'];
|
|
1324
|
+
return (0, jira_db_1.listOutbox)(this.db, state ? { state } : {}).map((row) => {
|
|
1325
|
+
let logicalState = null;
|
|
1326
|
+
let targetStatus = null;
|
|
1327
|
+
if (row.opType === 'transition') {
|
|
1328
|
+
try {
|
|
1329
|
+
const payload = JSON.parse(row.payload);
|
|
1330
|
+
if (payload && typeof payload === 'object') {
|
|
1331
|
+
if (logicalStates.includes(payload.logicalState))
|
|
1332
|
+
logicalState = payload.logicalState;
|
|
1333
|
+
if (typeof payload.targetStatus === 'string' && payload.targetStatus.trim())
|
|
1334
|
+
targetStatus = payload.targetStatus;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
catch {
|
|
1338
|
+
// Historical/corrupt payloads remain visible for diagnosis.
|
|
1339
|
+
}
|
|
1340
|
+
targetStatus ??= logicalState ? statusMap?.[logicalState] ?? null : null;
|
|
1341
|
+
}
|
|
1342
|
+
return { ...row, jiraKey: links.get(row.jiraIssueId) ?? null, logicalState, targetStatus };
|
|
1343
|
+
});
|
|
1313
1344
|
}
|
|
1314
1345
|
outboxCounts() {
|
|
1346
|
+
if ((0, jira_db_1.reconcileSupersededTransitions)(this.db) > 0)
|
|
1347
|
+
this.broadcastOutboxState();
|
|
1315
1348
|
return (0, jira_db_1.countOutboxByState)(this.db);
|
|
1316
1349
|
}
|
|
1350
|
+
retryOutbox(id) {
|
|
1351
|
+
const disposition = (0, jira_db_1.retryDeadOutbox)(this.db, id);
|
|
1352
|
+
if (disposition)
|
|
1353
|
+
this.broadcastOutboxState();
|
|
1354
|
+
return disposition;
|
|
1355
|
+
}
|
|
1317
1356
|
}
|
|
1318
1357
|
exports.JiraSyncManager = JiraSyncManager;
|
|
1319
1358
|
// ─── Error envelope for thrown client failures inside the walk ─────────────────
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.createJiraRouter = createJiraRouter;
|
|
37
4
|
const express_1 = require("express");
|
|
@@ -201,26 +168,26 @@ function createJiraRouter() {
|
|
|
201
168
|
router.get('/outbox', (req, res) => {
|
|
202
169
|
const c = ctx(req);
|
|
203
170
|
const state = req.query.state;
|
|
204
|
-
const valid = ['pending', 'inflight', 'done', 'dead'];
|
|
171
|
+
const valid = ['pending', 'inflight', 'done', 'dead', 'superseded'];
|
|
205
172
|
const filter = valid.includes(state) ? state : undefined;
|
|
206
173
|
res.json({ ops: c.jiraSyncManager.listOutbox(filter), counts: c.jiraSyncManager.outboxCounts() });
|
|
207
174
|
});
|
|
208
175
|
// POST /outbox/:id/retry — re-queue a dead-lettered op for a manual retry.
|
|
209
176
|
router.post('/outbox/:id/retry', async (req, res) => {
|
|
210
177
|
const c = ctx(req);
|
|
211
|
-
const id =
|
|
212
|
-
if (Number.
|
|
178
|
+
const id = Number(req.params.id);
|
|
179
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
213
180
|
res.status(400).json({ error: 'Invalid op id' });
|
|
214
181
|
return;
|
|
215
182
|
}
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
if (!ok) {
|
|
183
|
+
const disposition = c.jiraSyncManager.retryOutbox(id);
|
|
184
|
+
if (!disposition) {
|
|
219
185
|
res.status(404).json({ error: 'Op not found or not in dead state' });
|
|
220
186
|
return;
|
|
221
187
|
}
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
if (disposition === 'pending')
|
|
189
|
+
void c.jiraSyncManager.drainOnce().catch(() => undefined);
|
|
190
|
+
res.json({ ok: true, disposition });
|
|
224
191
|
});
|
|
225
192
|
// POST /specs — Add Spec when the project source is Jira: create the issue in
|
|
226
193
|
// Jira and materialize it locally. Keeps the generic local ticket-create path
|
package/server/dist/mcp/guide.js
CHANGED
|
@@ -385,9 +385,10 @@ can run Contract Refine require AI-spawn, including Explore conversions.
|
|
|
385
385
|
- **Plugins** (\`specrails_plugins\`): per-project MCP integrations; \`preview\`
|
|
386
386
|
the diff before \`install\`; a degraded plugin never blocks rail launches.
|
|
387
387
|
- **Profiles** (\`specrails_agents\`): per-project agent chains; the baseline
|
|
388
|
-
trio \`sr-architect\`/\`sr-developer\`/\`sr-reviewer\`
|
|
389
|
-
|
|
390
|
-
|
|
388
|
+
trio \`sr-architect\`/\`sr-developer\`/\`sr-reviewer\` are role identifiers
|
|
389
|
+
defined by the specrails-core runtime (no installed files) and are required
|
|
390
|
+
in every chain; routing rules end in a single terminal \`default: true\` rule;
|
|
391
|
+
requires specrails-core 4.1.0 or newer.
|
|
391
392
|
- **Setup** (\`specrails_setup\`): \`prerequisites\` → \`add_project\` →
|
|
392
393
|
\`install_config\` per provider → ONE \`install\` (provisions all chosen
|
|
393
394
|
providers; quick-only, offline) → poll \`checkpoints\` until
|
|
@@ -10,7 +10,7 @@ exports.runAgentRuntimeControl = runAgentRuntimeControl;
|
|
|
10
10
|
const agent_runtime_verification_suggestions_1 = require("./agent-runtime-verification-suggestions");
|
|
11
11
|
const agent_runtime_history_1 = require("./agent-runtime-history");
|
|
12
12
|
const agent_runtime_repositories_1 = require("./agent-runtime-repositories");
|
|
13
|
-
const
|
|
13
|
+
const agent_runtime_verification_log_1 = require("./agent-runtime-verification-log");
|
|
14
14
|
const agent_runtime_effective_config_1 = require("./agent-runtime-effective-config");
|
|
15
15
|
const node_child_process_1 = require("node:child_process");
|
|
16
16
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -93,66 +93,6 @@ function requiresConfiguredChecks(value) {
|
|
|
93
93
|
return Object.values(body.nodes ?? {}).some(node => node.kind === 'verify' && node.params?.commands === 'configured' && !node.params.additionalCommandsFrom && node.params.unverified !== true)
|
|
94
94
|
|| Object.values(body.components ?? {}).some(requiresConfiguredChecks);
|
|
95
95
|
}
|
|
96
|
-
/** Keep TAP failure diagnostics and totals in the readable log. The unfiltered
|
|
97
|
-
* event stream remains available as runtime evidence. State is invocation-local. */
|
|
98
|
-
function verificationLogFilter() {
|
|
99
|
-
const streams = new Map();
|
|
100
|
-
return text => (0, node_util_1.stripVTControlCharacters)(text).split(/(?<=\n)/).flatMap(line => {
|
|
101
|
-
const match = line.match(/^(\[verification [^\]]+\] )?(.*?)(?:\n)?$/);
|
|
102
|
-
if (!match)
|
|
103
|
-
return [line];
|
|
104
|
-
const key = match[1] ?? '', body = match[2].trim();
|
|
105
|
-
const state = streams.get(key) ?? { tap: false, failed: false, yaml: false, inputLines: null };
|
|
106
|
-
streams.set(key, state);
|
|
107
|
-
if (/AssertionError\b.*\bInput:\s*$/.test(body))
|
|
108
|
-
state.inputLines = 0;
|
|
109
|
-
else if (state.inputLines !== null) {
|
|
110
|
-
// Node prints assert.match's entire source input as quoted concatenation
|
|
111
|
-
// lines. Bound only that block; stacks, expected values and other streams
|
|
112
|
-
// remain visible and original events remain intact.
|
|
113
|
-
if (/^["'`]/.test(body)) {
|
|
114
|
-
state.inputLines++;
|
|
115
|
-
if (state.inputLines > 2)
|
|
116
|
-
return state.inputLines === 3 ? [key + '[Assertion input shortened; inspect raw evidence]\n'] : [];
|
|
117
|
-
}
|
|
118
|
-
else if (!body)
|
|
119
|
-
return [];
|
|
120
|
-
else
|
|
121
|
-
state.inputLines = null;
|
|
122
|
-
}
|
|
123
|
-
if (/^[✔✓]\s/.test(body))
|
|
124
|
-
return [];
|
|
125
|
-
if (/^[✖✗]\s/.test(body)) {
|
|
126
|
-
state.failed = true;
|
|
127
|
-
return [line];
|
|
128
|
-
}
|
|
129
|
-
if (/^TAP version |^# Subtest:/.test(body)) {
|
|
130
|
-
state.tap = true;
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
if (/^(?:not )?ok \d+\b/.test(body)) {
|
|
134
|
-
state.tap = true;
|
|
135
|
-
state.failed = body.startsWith('not ok');
|
|
136
|
-
state.yaml = false;
|
|
137
|
-
return state.failed ? [line] : [];
|
|
138
|
-
}
|
|
139
|
-
if (!state.tap)
|
|
140
|
-
return [line];
|
|
141
|
-
if (body === '---') {
|
|
142
|
-
state.yaml = true;
|
|
143
|
-
return state.failed ? [line] : [];
|
|
144
|
-
}
|
|
145
|
-
if (body === '...') {
|
|
146
|
-
state.yaml = false;
|
|
147
|
-
return state.failed ? [line] : [];
|
|
148
|
-
}
|
|
149
|
-
if (state.yaml)
|
|
150
|
-
return state.failed ? [line] : [];
|
|
151
|
-
if (/^1\.\.\d+$/.test(body) || !body)
|
|
152
|
-
return [];
|
|
153
|
-
return [line];
|
|
154
|
-
}).map(line => line.length > 2_000 ? line.slice(0, 1_900) + '\n[Long diagnostic line shortened; inspect raw evidence]\n' : line).join('');
|
|
155
|
-
}
|
|
156
96
|
exports.RUNTIME_HOST_ENV_KEYS = [
|
|
157
97
|
'SPECRAILS_REPO_DIR', 'SPECRAILS_GIT_AUTO', 'SPECRAILS_REPO_MAP_PATH',
|
|
158
98
|
'SPECRAILS_EXECUTION_CONTEXT', 'SPECRAILS_EXECUTION_MANIFEST', 'SPECRAILS_REPOSITORIES',
|
|
@@ -208,14 +148,18 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
208
148
|
if (!Array.isArray(admittedContext.repositories) || !admittedContext.repositories.length || admittedContext.repositories.some(repo => !repo || typeof repo.id !== 'string' || !repo.id))
|
|
209
149
|
throw new Error('Core context is missing its repository scope');
|
|
210
150
|
const source = (0, node_fs_1.existsSync)(options.configPath) ? 'project-role' : 'default';
|
|
211
|
-
|
|
151
|
+
// The engine owns the factory role definitions; the user's global overrides sit on top. The Implement
|
|
152
|
+
// rail merges them under any project text, and loop recipes resolve their `inherit` definitions here.
|
|
153
|
+
const effectiveRolePrompts = { ...(await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)() };
|
|
154
|
+
const loopConfig = options.loopConfig && (0, agent_runtime_settings_1.resolveInheritedRolePrompts)(options.loopConfig, effectiveRolePrompts);
|
|
155
|
+
const { config, origins } = (0, agent_runtime_effective_config_1.resolveEffectiveRuntimeConfig)(loopConfig ? (0, agent_runtime_settings_1.loadLoopRuntimeConfig)(options.configPath, loopConfig, options.providerOverride ?? { provider: options.defaultProvider ?? 'claude' }) : (0, agent_runtime_settings_1.loadRuntimeConfigFile)(options.configPath, options.defaultProvider), {
|
|
212
156
|
repositoryIds: admittedContext.repositories.map(repo => repo.id), source: options.loopConfig ? 'loop-role' : source, providerOverride: options.loopConfig ? undefined : options.providerOverride,
|
|
213
157
|
});
|
|
214
158
|
config.verification = scopedHostChecks(config.verification, admittedContext.repositories);
|
|
215
159
|
if (definitionEngine)
|
|
216
160
|
completeHostChecks(config, admittedContext.repositories);
|
|
217
|
-
if (!
|
|
218
|
-
config.rolePrompts = { ...
|
|
161
|
+
if (!loopConfig)
|
|
162
|
+
config.rolePrompts = { ...effectiveRolePrompts, ...config.rolePrompts };
|
|
219
163
|
// Core rejects unknown connection keys: drop the desktop-only local-engine
|
|
220
164
|
// fields (label/defaultModel/rates/supportsReasoningEffort) before Core sees it.
|
|
221
165
|
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
@@ -356,7 +300,13 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
356
300
|
callback?.();
|
|
357
301
|
}
|
|
358
302
|
catch { /* Logging cannot replay a workflow. */ } };
|
|
359
|
-
const readableVerification =
|
|
303
|
+
const readableVerification = (0, agent_runtime_verification_log_1.createVerificationLogProjection)();
|
|
304
|
+
const readableLines = (lines) => {
|
|
305
|
+
for (const line of lines)
|
|
306
|
+
observe(() => line.attemptId
|
|
307
|
+
? options.onLine?.(line.text, 'stdout', { attemptId: line.attemptId })
|
|
308
|
+
: options.onLine?.(line.text));
|
|
309
|
+
};
|
|
360
310
|
const lines = (0, node_readline_1.createInterface)({ input: child.stdout });
|
|
361
311
|
lines.on('line', line => {
|
|
362
312
|
if (line.length > 2_000_000) {
|
|
@@ -388,6 +338,10 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
388
338
|
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL');
|
|
389
339
|
return;
|
|
390
340
|
}
|
|
341
|
+
const boundary = event.type === 'workflow-event' || event.type === 'runtime-result';
|
|
342
|
+
// Flush buffered output before the projection advances the current step.
|
|
343
|
+
if (boundary)
|
|
344
|
+
readableLines(readableVerification.project(event));
|
|
391
345
|
try {
|
|
392
346
|
options.onRuntimeEvent?.(event);
|
|
393
347
|
}
|
|
@@ -400,6 +354,8 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
400
354
|
if (event.type === 'runtime-graph')
|
|
401
355
|
graph = event;
|
|
402
356
|
observe(() => options.onRawLine?.(event.repositories ? JSON.stringify(event) : line));
|
|
357
|
+
if (!boundary)
|
|
358
|
+
readableLines(readableVerification.project(event));
|
|
403
359
|
if (event.type === 'runtime-result') {
|
|
404
360
|
if (result)
|
|
405
361
|
invalidProtocol = true;
|
|
@@ -435,11 +391,6 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
435
391
|
observe(() => options.onLine?.(`[${role}]${scope} ${payload.tool}${payload.detail ? ' ' + payload.detail : ''}\n`));
|
|
436
392
|
}
|
|
437
393
|
}
|
|
438
|
-
else if (event.type === 'verification-output' && typeof event.text === 'string') {
|
|
439
|
-
const readable = readableVerification(String(event.text));
|
|
440
|
-
if (readable)
|
|
441
|
-
observe(() => options.onLine?.(readable));
|
|
442
|
-
}
|
|
443
394
|
else if (event.type === 'span') {
|
|
444
395
|
// Trace spans are telemetry; the raw line is already recorded for diagnostics.
|
|
445
396
|
}
|
|
@@ -461,6 +412,7 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
461
412
|
if (timer)
|
|
462
413
|
clearTimeout(timer);
|
|
463
414
|
lines.close();
|
|
415
|
+
readableLines(readableVerification.flush());
|
|
464
416
|
const usage = result?.invocationUsage;
|
|
465
417
|
const known = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
466
418
|
const cost = known(usage?.costUsd), tokensIn = known(usage?.inputTokens), tokensOut = known(usage?.outputTokens);
|
|
@@ -26,6 +26,7 @@ const agent_runtime_bridge_1 = require("./agent-runtime-bridge");
|
|
|
26
26
|
const core_node_runtime_1 = require("../../../core-node-runtime");
|
|
27
27
|
const win_spawn_1 = require("../../../util/win-spawn");
|
|
28
28
|
const workspace_resolution_1 = require("../../../workspace-resolution");
|
|
29
|
+
const project_env_1 = require("../../../project-env");
|
|
29
30
|
const loop_runs_store_1 = require("../../loops/runtime/loop-runs-store");
|
|
30
31
|
const multi_repo_execution_store_1 = require("../../delivery/runtime/multi-repo-execution-store");
|
|
31
32
|
const db_1 = require("../../../db");
|
|
@@ -309,7 +310,7 @@ class AgentRuntimeControls {
|
|
|
309
310
|
const host = JSON.parse(node_fs_1.default.readFileSync(hostFile, 'utf8'));
|
|
310
311
|
if (host.schemaVersion !== 1 || typeof host.cwd !== 'string' || ![frozen.backlogRoot, ...frozen.repositories.map((repository) => repository.path)].includes(host.cwd) || !host.env || typeof host.env !== 'object' || Array.isArray(host.env) || Object.entries(host.env).some(([key, value]) => !agent_runtime_bridge_1.RUNTIME_HOST_ENV_KEYS.includes(key) || typeof value !== 'string'))
|
|
311
312
|
throw new RuntimeControlError(409, 'runtime_host_invalid', 'The saved runtime host settings are invalid');
|
|
312
|
-
const env = { ...(0, workspace_resolution_1.resolveLoopBaseEnv)(this.ctx.project) };
|
|
313
|
+
const env = { ...(0, workspace_resolution_1.resolveLoopBaseEnv)(this.ctx.project, undefined, (0, project_env_1.applyWorktreeEnvPassthrough)(this.ctx.db, process.env)) };
|
|
313
314
|
for (const key of agent_runtime_bridge_1.RUNTIME_HOST_ENV_KEYS)
|
|
314
315
|
delete env[key];
|
|
315
316
|
Object.assign(env, host.env, { SPECRAILS_EXECUTION_CONTEXT: file });
|
|
@@ -541,15 +542,17 @@ class AgentRuntimeControls {
|
|
|
541
542
|
loopDurationBaseline: previous.total_duration_ms, completedDurationMs: 0,
|
|
542
543
|
iterationCount: previous.iteration_count, activeTurnStartedAtMs: startedAtMs, lastActivityAtMs: startedAtMs,
|
|
543
544
|
});
|
|
544
|
-
const logLine = (line, source = 'stdout') => {
|
|
545
|
+
const logLine = (line, source = 'stdout', metadata) => {
|
|
545
546
|
if (this.disposed)
|
|
546
547
|
return;
|
|
548
|
+
const attemptId = metadata?.attemptId;
|
|
549
|
+
const attribution = typeof attemptId === 'string' && attemptId.trim() && attemptId.length <= 256 ? { attemptId } : {};
|
|
547
550
|
try {
|
|
548
|
-
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: 'log', source, payload: JSON.stringify({ line: line.replace(/\n$/, '') }) });
|
|
551
|
+
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: 'log', source, payload: JSON.stringify({ line: line.replace(/\n$/, ''), ...attribution }) });
|
|
549
552
|
}
|
|
550
553
|
catch { /* events table best-effort */ }
|
|
551
554
|
try {
|
|
552
|
-
this.ctx.broadcast?.({ type: 'log', source, line, timestamp: new Date().toISOString(), processId: runId });
|
|
555
|
+
this.ctx.broadcast?.({ type: 'log', source, line, timestamp: new Date().toISOString(), processId: runId, ...attribution });
|
|
553
556
|
}
|
|
554
557
|
catch { /* persisted log remains authoritative */ }
|
|
555
558
|
};
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AgentRuntimeConfigError = exports.REVIEW_THRESHOLD_FLOORS = exports.MAX_OUTPUT_TOKENS_MAX = exports.MAX_OUTPUT_TOKENS_MIN = exports.CONTEXT_WINDOW_TOKENS_MAX = exports.CONTEXT_WINDOW_TOKENS_MIN = exports.RUNTIME_ROLE_ID = void 0;
|
|
6
|
+
exports.INHERIT_ROLE_PROMPT = exports.AgentRuntimeConfigError = exports.REVIEW_THRESHOLD_FLOORS = exports.MAX_OUTPUT_TOKENS_MAX = exports.MAX_OUTPUT_TOKENS_MIN = exports.CONTEXT_WINDOW_TOKENS_MAX = exports.CONTEXT_WINDOW_TOKENS_MIN = exports.RUNTIME_ROLE_ID = void 0;
|
|
7
7
|
exports.stripDesktopConnectionFields = stripDesktopConnectionFields;
|
|
8
8
|
exports.coreConnectionFieldGates = coreConnectionFieldGates;
|
|
9
9
|
exports.validateLoopRuntimeSettings = validateLoopRuntimeSettings;
|
|
@@ -18,6 +18,8 @@ exports.saveRuntimeProviders = saveRuntimeProviders;
|
|
|
18
18
|
exports.loadRuntimeConfigFile = loadRuntimeConfigFile;
|
|
19
19
|
exports.loadAgentRuntimeConfig = loadAgentRuntimeConfig;
|
|
20
20
|
exports.saveAgentRuntimeConfig = saveAgentRuntimeConfig;
|
|
21
|
+
exports.inheritedRolePrompt = inheritedRolePrompt;
|
|
22
|
+
exports.resolveInheritedRolePrompts = resolveInheritedRolePrompts;
|
|
21
23
|
exports.validateRuntimeRolePrompts = validateRuntimeRolePrompts;
|
|
22
24
|
exports.loadRuntimeRolePrompts = loadRuntimeRolePrompts;
|
|
23
25
|
exports.saveRuntimeRolePrompts = saveRuntimeRolePrompts;
|
|
@@ -419,6 +421,27 @@ function saveAgentRuntimeConfig(project, input) {
|
|
|
419
421
|
return config;
|
|
420
422
|
}
|
|
421
423
|
const PROMPT_ROLES = [...ROLES, 'fixer'];
|
|
424
|
+
/** A loop recipe definition that follows the engine: resolved at launch to the selected Core's factory definition plus the user's global override. */
|
|
425
|
+
exports.INHERIT_ROLE_PROMPT = 'inherit';
|
|
426
|
+
const INHERITED_ROLE_PROMPT = /^inherit:(architect|developer|reviewer|fixer)$/;
|
|
427
|
+
/** The prompt a declared recipe role carries when it follows a built-in definition (`inherit:<role>`), or the recipe's own text. */
|
|
428
|
+
function inheritedRolePrompt(prompts, role) {
|
|
429
|
+
const value = prompts?.[role];
|
|
430
|
+
return value === undefined || value === exports.INHERIT_ROLE_PROMPT ? `${exports.INHERIT_ROLE_PROMPT}:${role}` : value;
|
|
431
|
+
}
|
|
432
|
+
/** Replace `inherit` definitions (built-in roles) and `inherit:<role>` prompts (declared roles) with the effective engine definitions. Explicit recipe text is kept verbatim. */
|
|
433
|
+
function resolveInheritedRolePrompts(settings, effective) {
|
|
434
|
+
// A Core predating the fixer stance has no fixer default: the fixer then follows the developer definition.
|
|
435
|
+
const resolved = { ...effective, fixer: effective.fixer ?? effective.developer };
|
|
436
|
+
const lookup = (role) => resolved[role];
|
|
437
|
+
const rolePrompts = settings.rolePrompts && Object.fromEntries(Object.entries(settings.rolePrompts).map(([role, text]) => [role, text === exports.INHERIT_ROLE_PROMPT ? lookup(role) ?? text : text]));
|
|
438
|
+
const roles = settings.roles && Object.fromEntries(Object.entries(settings.roles).map(([id, role]) => {
|
|
439
|
+
const inherited = role.prompt?.match(INHERITED_ROLE_PROMPT);
|
|
440
|
+
const text = inherited ? lookup(inherited[1]) : undefined;
|
|
441
|
+
return [id, text ? { ...role, prompt: text } : role];
|
|
442
|
+
}));
|
|
443
|
+
return { ...settings, ...(rolePrompts ? { rolePrompts } : {}), ...(roles ? { roles } : {}) };
|
|
444
|
+
}
|
|
422
445
|
function validateRuntimeRolePrompts(input, extraRoles = []) {
|
|
423
446
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
424
447
|
throw new AgentRuntimeConfigError('Role prompts must be an object');
|