specrails-desktop 2.25.0 → 2.26.1
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-BGRTOOhT.js → ActivityFeedPage-C1LftFSE.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-DvDLxsUy.js → AgentBrowserCapture-B5prW-V0.js} +1 -1
- package/client/dist/assets/{AgentModeCodePane-DfwctjzN.js → AgentModeCodePane-b6UmdjwB.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-G3WrS-sE.js → AgentModeJobsPane-DW3Wj5oi.js} +1 -1
- package/client/dist/assets/{AgentsPage-DGFFijMC.js → AgentsPage-DBMnhdlq.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-hoJmlEpk.js → AnalyticsPage-DjFf1OxU.js} +1 -1
- package/client/dist/assets/{BarChart-BEhAXVK-.js → BarChart-nmDbbUiR.js} +1 -1
- package/client/dist/assets/{CodePage-B4zB9DVC.js → CodePage-DHd46Sm7.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-CBX-Qpxd.js → DesktopAnalyticsPage-ee0bpk-F.js} +1 -1
- package/client/dist/assets/{DocsDialog-lTsnNdAM.js → DocsDialog-CGUzB1zX.js} +1 -1
- package/client/dist/assets/{DocsPage-CkvdGroT.js → DocsPage-96iws84I.js} +1 -1
- package/client/dist/assets/{ExportDropdown-Da98gR-P.js → ExportDropdown-Dzk2Lkxw.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-Cf9o4eZq.js → IntegrationsPage-Bta2lAhq.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-C8N-Bbnn.js → InteractiveJobComposer-B9S_ZGJS.js} +5 -5
- package/client/dist/assets/{JobDetailModal-BZVwIIEx.js → JobDetailModal-DhrwU2z0.js} +1 -1
- package/client/dist/assets/{JobDetailPage-CQewQ1zy.js → JobDetailPage-BnhoXkql.js} +1 -1
- package/client/dist/assets/{JobsPage-CB4wOxmE.js → JobsPage-BBT9fyhg.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-B9Y5gt08.js → LoopBuilderPage-5ucN9Hq9.js} +2 -2
- package/client/dist/assets/{LoopPreviewModal-kxQ5kV1t.js → LoopPreviewModal-wKAts-Wf.js} +1 -1
- package/client/dist/assets/{LoopsPage-RysW-6sC.js → LoopsPage-COyxf6Pa.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-BHN5qsGT.js → ProjectSettingsDialog-CcToBI-Z.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-DW6lZCIs.js → TemplatePreviewModal-_Dy6ceXg.js} +1 -1
- package/client/dist/assets/agent-BmH52J4j.js +1 -0
- package/client/dist/assets/agent-Bxb9RRNW.js +1 -0
- package/client/dist/assets/agent-Cg49-TGZ.js +1 -0
- package/client/dist/assets/agent-DgDtg-0Y2.js +1 -0
- package/client/dist/assets/agent-DolxzTol.js +1 -0
- package/client/dist/assets/agent-DwDR2Exf2.js +1 -0
- package/client/dist/assets/agent-_tfCgFUk.js +1 -0
- package/client/dist/assets/agent-gFi-qvxO2.js +1 -0
- package/client/dist/assets/{brain-B9dkGuAN.js → brain-BJ2Gd98j.js} +1 -1
- package/client/dist/assets/code-CwnBPPyA.js +1 -0
- package/client/dist/assets/dashboard-90r0QcUc2.js +1 -0
- package/client/dist/assets/dashboard-B0C6mJP-.js +1 -0
- package/client/dist/assets/dashboard-BOx0FR1L2.js +1 -0
- package/client/dist/assets/dashboard-BcjUOLJr.js +1 -0
- package/client/dist/assets/dashboard-C27kNpjn.js +1 -0
- package/client/dist/assets/dashboard-DJAj2EbH.js +1 -0
- package/client/dist/assets/dashboard-DNd1Gobs.js +1 -0
- package/client/dist/assets/dashboard-DZWrDAYm2.js +1 -0
- package/client/dist/assets/{dist-js-Bbd7Wzdw.js → dist-js-CXAsO0Z8.js} +1 -1
- package/client/dist/assets/{dist-js-BW5-IZTP.js → dist-js-D7smLA35.js} +1 -1
- package/client/dist/assets/index-B3ejvKSt.js +156 -0
- package/client/dist/assets/index-C695aPdV.css +2 -0
- package/client/dist/assets/{lib-EIkIPf26.js → lib-BIHmUkcn.js} +1 -1
- package/client/dist/assets/{loops-YYV2bwjd.js → loops-Brnx4Fji.js} +1 -1
- package/client/dist/assets/{loops-hXgncEpF2.js → loops-BvqQ-bHR2.js} +1 -1
- package/client/dist/assets/{loops-BvEenWBy.js → loops-CvOWsi5r.js} +1 -1
- package/client/dist/assets/{loops-DjOtVXRo.js → loops-D6D3XHjj.js} +1 -1
- package/client/dist/assets/{loops-HTHf-xu32.js → loops-DJNhp17p2.js} +1 -1
- package/client/dist/assets/{loops-kYbydDyN.js → loops-K5_HXfJa.js} +1 -1
- package/client/dist/assets/loops-LIgO_-eM.js +1 -0
- package/client/dist/assets/{loops-DNGiYKll2.js → loops-vhJdbcz92.js} +1 -1
- package/client/dist/assets/{settings-BsJMsWGc.js → settings-BHdPchJP.js} +1 -1
- package/client/dist/assets/{settings-lFoaYT-s2.js → settings-BeCczBFS2.js} +1 -1
- package/client/dist/assets/{settings-DZJeXoRV.js → settings-BlLi719f.js} +1 -1
- package/client/dist/assets/{settings-BOHOmHkb.js → settings-C_nY14Yz.js} +1 -1
- package/client/dist/assets/{settings-D6qY9_UI.js → settings-CaZ26a0S.js} +1 -1
- package/client/dist/assets/{settings-BlEA_7h02.js → settings-D54_cgl12.js} +1 -1
- package/client/dist/assets/settings-D9bDDe2O.js +1 -0
- package/client/dist/assets/{settings-TNga5gIF2.js → settings-cn0tbve62.js} +1 -1
- package/client/dist/assets/{setup-iY8vH8pB.js → setup-7J8TjG4O.js} +1 -1
- package/client/dist/assets/{setup-D674kWQH.js → setup-BXpw07r-.js} +1 -1
- package/client/dist/assets/{setup-B590KsTX.js → setup-BiduSX9d.js} +1 -1
- package/client/dist/assets/{setup-CmU6bTCV2.js → setup-BuQNKztt2.js} +1 -1
- package/client/dist/assets/{setup-CEj6uY3-2.js → setup-BwMovM9z2.js} +1 -1
- package/client/dist/assets/{setup-BkyGoby6.js → setup-D84y3wx-.js} +1 -1
- package/client/dist/assets/{setup-CbIerFGf2.js → setup-DU0ULX8q2.js} +1 -1
- package/client/dist/assets/{setup-VNqXyB6Y.js → setup-RiUHzq7E.js} +1 -1
- package/client/dist/assets/{upload-BCMl-nCg.js → upload-xdQuJvXN.js} +1 -1
- package/client/dist/assets/{value-CG-amSq1.js → value-Bk2f9AUg.js} +1 -1
- package/client/dist/index.html +22 -9
- package/docs/agy-cli-provider-study.md +1 -1
- package/docs/codex.md +2 -2
- package/docs/gemini-cli-provider-study.md +2 -2
- package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +1 -1
- package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/internals/api-reference.md +3 -3
- package/docs/internals/companion-rails-as-loops-contract.md +6 -6
- package/docs/internals/configuration.md +1 -1
- package/docs/internals/interactive-jobs.md +3 -3
- package/docs/internals/safe-pr-review-flow.md +1 -1
- package/docs/internals/visual-design-audit.md +2 -2
- package/docs/running-pipelines.md +1 -1
- package/package.json +1 -1
- package/server/dist/agent-chat-manager.js +16 -2
- package/server/dist/agent-chat-router.js +66 -1
- package/server/dist/agent-context-resolver.js +301 -0
- package/server/dist/agent-operator-prompt.js +58 -11
- package/server/dist/agent-store.js +24 -12
- package/server/dist/db.js +17 -17
- package/server/dist/desktop-db.js +6 -0
- package/server/dist/desktop-router.js +9 -2
- package/server/dist/feature-flags.js +2 -2
- package/server/dist/index.js +1 -1
- package/server/dist/interactive-job-session.js +3 -3
- package/server/dist/loop-command-catalog.js +11 -11
- package/server/dist/loop-factory.js +7 -7
- package/server/dist/loop-graph.js +2 -2
- package/server/dist/loop-run-manager.js +1 -1
- package/server/dist/mcp/guide.js +41 -10
- package/server/dist/mcp/tools/app-settings.js +18 -2
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/rails.js +29 -9
- package/server/dist/mcp/tools/support.js +324 -0
- package/server/dist/mobile/mobile-router.js +1 -1
- package/server/dist/project-router-jobs.js +1 -1
- package/server/dist/project-router-loop-runs.js +1 -1
- package/server/dist/project-router-settings.js +5 -5
- package/server/dist/queue-manager.js +27 -27
- package/server/dist/rail-isolated-launch.js +5 -1
- package/server/dist/rails-router.js +76 -27
- package/client/dist/assets/agent-4ARs0uac2.js +0 -1
- package/client/dist/assets/agent-C55wwsUX.js +0 -1
- package/client/dist/assets/agent-C_kqyrXo.js +0 -1
- package/client/dist/assets/agent-Df3lnHMu.js +0 -1
- package/client/dist/assets/agent-G6Zn2gpr.js +0 -1
- package/client/dist/assets/agent-MQhun7cv.js +0 -1
- package/client/dist/assets/agent-NqlER4Yh2.js +0 -1
- package/client/dist/assets/agent-OTGI6t7e2.js +0 -1
- package/client/dist/assets/code-CqCcRThR.js +0 -1
- package/client/dist/assets/dashboard-4WX7agHF2.js +0 -1
- package/client/dist/assets/dashboard-5Z_esXIB.js +0 -1
- package/client/dist/assets/dashboard-DQ-po1MG2.js +0 -1
- package/client/dist/assets/dashboard-IbEztmJ52.js +0 -1
- package/client/dist/assets/dashboard-nc-Raovv.js +0 -1
- package/client/dist/assets/dashboard-ojWdDq6S.js +0 -1
- package/client/dist/assets/dashboard-oqS7VRrK.js +0 -1
- package/client/dist/assets/dashboard-zfoJcdFW.js +0 -1
- package/client/dist/assets/index-BtAks9nm.css +0 -2
- package/client/dist/assets/index-DC-REAcL.js +0 -155
- package/client/dist/assets/loops-CBiWd9Hf.js +0 -1
- package/client/dist/assets/settings-Mr7MnJbf.js +0 -1
|
@@ -17,6 +17,7 @@ const code_1 = require("./code");
|
|
|
17
17
|
const setup_1 = require("./setup");
|
|
18
18
|
const analytics_1 = require("./analytics");
|
|
19
19
|
const git_1 = require("./git");
|
|
20
|
+
const support_1 = require("./support");
|
|
20
21
|
/**
|
|
21
22
|
* The full MCP tool catalog. Domain-facade tools each expose an `action` enum
|
|
22
23
|
* and call the app's REST API over loopback (see apiCall). Meta tools receive a
|
|
@@ -41,6 +42,7 @@ function buildToolSpecs() {
|
|
|
41
42
|
specs.push(...(0, setup_1.setupTools)());
|
|
42
43
|
specs.push(...(0, analytics_1.analyticsTools)());
|
|
43
44
|
specs.push(...(0, git_1.gitTools)());
|
|
45
|
+
specs.push(...(0, support_1.supportTools)());
|
|
44
46
|
// Meta last (needs the full list for search/describe)
|
|
45
47
|
specs.push(...(0, meta_1.metaTools)(() => specs));
|
|
46
48
|
return specs;
|
|
@@ -7,6 +7,24 @@ const types_1 = require("./types");
|
|
|
7
7
|
* efforts outside it (codex 'minimal', claude 'xhigh') are never defaulted —
|
|
8
8
|
* a default must not 400 a launch that would succeed without it. */
|
|
9
9
|
const RAIL_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function prDeliveryContinuesTickets(raw, ticketIds) {
|
|
14
|
+
if (!isRecord(raw))
|
|
15
|
+
return false;
|
|
16
|
+
const decision = raw.decision;
|
|
17
|
+
if (decision !== 'pr_draft' && decision !== 'pr_ready')
|
|
18
|
+
return false;
|
|
19
|
+
if (typeof raw.prUrl !== 'string' || !raw.prUrl)
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof raw.branch !== 'string' || !raw.branch)
|
|
22
|
+
return false;
|
|
23
|
+
if (!Array.isArray(raw.ticketIds))
|
|
24
|
+
return false;
|
|
25
|
+
const covered = new Set(raw.ticketIds.filter((id) => typeof id === 'number'));
|
|
26
|
+
return ticketIds.length > 0 && ticketIds.every((id) => covered.has(id));
|
|
27
|
+
}
|
|
10
28
|
/**
|
|
11
29
|
* Rails domain facade. A "rail" is a numbered launch slot per project: it holds
|
|
12
30
|
* a set of assigned ticket IDs plus a mode/profile/engine/name, and launching it
|
|
@@ -27,10 +45,11 @@ function railsTools() {
|
|
|
27
45
|
'Actions: list (rails + active jobs/loop runs), create_rail (add a new rail slot; returns its railIndex), set_tickets (assign ticket IDs), set_profile (default agent profile, null=legacy), ' +
|
|
28
46
|
'set_engine (provider override, null=primary), set_name (display label, null clears), ' +
|
|
29
47
|
'launch (ai-spawn — spawns claude/codex/gemini CLI job(s) that WRITE CODE, RUN TESTS, COMMIT, and INCUR TOKEN COST; returns 202 with jobId/jobIds/loopRunIds), ' +
|
|
30
|
-
'launch_all (ai-spawn — launches EVERY rail that has tickets and no active run/pending PR decision, in parallel, using each rail\'s stored mode/engine/profile; returns per-rail outcomes with skip reasons), ' +
|
|
48
|
+
'launch_all (ai-spawn — launches EVERY rail that has tickets and no active run/uncontinuable pending PR decision, in parallel, using each rail\'s stored mode/engine/profile; returns per-rail outcomes with skip reasons), ' +
|
|
31
49
|
'stop (destructive — kills all active jobs and loop runs for the rail). ' +
|
|
32
|
-
'For on_review tickets with an already-open GitHub PR, launch automatically tries to continue that PR head branch; Jira-linked in_progress tickets can do the same when the PR match is explicit; fresh tickets still start from the project integration branch. ' +
|
|
50
|
+
'For on_review tickets with an already-open GitHub PR (including a published pr_ready delivery), launch automatically tries to continue that PR head branch; Jira-linked in_progress tickets can do the same when the PR match is explicit; fresh tickets still start from the project integration branch. ' +
|
|
33
51
|
'When launched from the in-app agent chat without an explicit aiEngine, the engine defaults to your conversation\'s provider (pass aiEngine to override; launch_all always uses each rail\'s stored engine). ' +
|
|
52
|
+
'User-facing naming: call the free-form autonomous mode "Freestyle"; use "freestyle" as the canonical API enum value for that same capability. ' +
|
|
34
53
|
'NAMING: railIndex is the 0-BASED internal identity; the dashboard shows rails 1-based ("Rail N" = railIndex N-1). When talking to the user, ALWAYS say "Rail <railIndex + 1>" (or the rail\'s custom name) — results include railLabel with the correct user-facing label.',
|
|
35
54
|
hintTier: 'read',
|
|
36
55
|
tier: (a) => {
|
|
@@ -79,13 +98,13 @@ function railsTools() {
|
|
|
79
98
|
.describe('Rail display label (<=60 chars); null/empty clears to default (set_name). Also the optional initial name for create_rail.'),
|
|
80
99
|
// launch
|
|
81
100
|
mode: zod_1.z
|
|
82
|
-
.enum(['implement', 'batch-implement', '
|
|
101
|
+
.enum(['implement', 'batch-implement', 'freestyle', 'loop'])
|
|
83
102
|
.optional()
|
|
84
|
-
.describe('Launch mode (launch; default "implement"). "
|
|
103
|
+
.describe('Launch mode (launch; default "implement"). Use "freestyle" as the canonical API enum value for Freestyle: a free-form autonomous prompt sent straight to Claude, one job per ticket. In prose, call it "Freestyle". Loop runs an app-driven loop per ticket.'),
|
|
85
104
|
model: zod_1.z
|
|
86
105
|
.string()
|
|
87
106
|
.optional()
|
|
88
|
-
.describe('Model for launch. Freestyle
|
|
107
|
+
.describe('Model for launch. Freestyle is Claude-only and takes a Claude alias (haiku | sonnet | opus | fable); its API mode value is "freestyle". For loop mode, any model string valid for the rail\'s provider (claude/codex/gemini).'),
|
|
89
108
|
interactive: zod_1.z
|
|
90
109
|
.boolean()
|
|
91
110
|
.optional()
|
|
@@ -235,7 +254,7 @@ function railsTools() {
|
|
|
235
254
|
return {
|
|
236
255
|
...r,
|
|
237
256
|
railLabel,
|
|
238
|
-
hint: `Launch accepted (202) on ${railLabel} (isolated worktree, PR flow active) — tell the user it runs on "${railLabel}" (UI labels are 1-based). If the assigned spec is on_review with a matching open PR, or is Jira-linked in_progress with an explicit PR match, Specrails continues that PR branch automatically; otherwise it starts a fresh branch from the integration branch. The PR-decision card will appear here and on the rail header when it settles. Use specrails_watch with the returned loopRunIds to await completion only if asked. Rails run for minutes; pass untilMs up to 600000 and re-watch on timeout.`,
|
|
257
|
+
hint: `Launch accepted (202) on ${railLabel} (isolated worktree, PR flow active) — tell the user it runs on "${railLabel}" (UI labels are 1-based). If the assigned spec is on_review with a matching open PR, including an already-published pr_ready PR, or is Jira-linked in_progress with an explicit PR match, Specrails continues that PR branch automatically; otherwise it starts a fresh branch from the integration branch. The PR-decision card will appear here and on the rail header when it settles. Use specrails_watch with the returned loopRunIds to await completion only if asked. Rails run for minutes; pass untilMs up to 600000 and re-watch on timeout.`,
|
|
239
258
|
};
|
|
240
259
|
}
|
|
241
260
|
case 'launch_all': {
|
|
@@ -243,8 +262,9 @@ function railsTools() {
|
|
|
243
262
|
// because each launch isolates its work in per-ticket git worktrees
|
|
244
263
|
// (allocation is serialized per repo by the launch path itself).
|
|
245
264
|
// Eligibility mirrors the dashboard's Launch-all control: the rail
|
|
246
|
-
// must hold tickets, have no active job/loop run, and no
|
|
247
|
-
// PR delivery.
|
|
265
|
+
// must hold tickets, have no active job/loop run, and no uncontinuable
|
|
266
|
+
// PR delivery. A draft/published PR covering the rail's tickets is
|
|
267
|
+
// intentionally continuable. Each rail launches with its OWN stored config
|
|
248
268
|
// (mode/engine/profile) — conversation-provider defaults do NOT
|
|
249
269
|
// apply here (they would clobber per-rail engines).
|
|
250
270
|
const snapshot = (await (0, types_1.apiCall)(ctx, 'GET', base));
|
|
@@ -266,7 +286,7 @@ function railsTools() {
|
|
|
266
286
|
results.push({ railIndex: idx, outcome: 'skipped', reason: 'already-running', ticketIds });
|
|
267
287
|
continue;
|
|
268
288
|
}
|
|
269
|
-
if (key in prDeliveries) {
|
|
289
|
+
if (key in prDeliveries && !prDeliveryContinuesTickets(prDeliveries[key], ticketIds)) {
|
|
270
290
|
results.push({ railIndex: idx, outcome: 'skipped', reason: 'pr-decision-pending', ticketIds });
|
|
271
291
|
continue;
|
|
272
292
|
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.supportTools = supportTools;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const mcp_tiers_1 = require("../mcp-tiers");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const projects_1 = require("./projects");
|
|
8
|
+
const SUPPORT_TOPICS = [
|
|
9
|
+
'installation',
|
|
10
|
+
'project_setup',
|
|
11
|
+
'provider_cli',
|
|
12
|
+
'mcp_connection',
|
|
13
|
+
'agent_chat',
|
|
14
|
+
'framework_install',
|
|
15
|
+
'specs_backlog',
|
|
16
|
+
'rails_jobs',
|
|
17
|
+
'git_pr',
|
|
18
|
+
'analytics_costs',
|
|
19
|
+
'plugins_integrations',
|
|
20
|
+
'general_usage',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Support triage and repair entry point. This is intentionally NOT a spec
|
|
24
|
+
* creation path: it keeps user-help requests in the conversation and routes
|
|
25
|
+
* framework repairs through explicit support actions.
|
|
26
|
+
*/
|
|
27
|
+
function supportTools() {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
name: 'specrails_support',
|
|
31
|
+
title: 'Support & troubleshooting',
|
|
32
|
+
description: 'Support and troubleshooting for Specrails installation and usage problems. Use this when the user needs help setting up Specrails, adding/provisioning a project, fixing missing agents/skills/commands from the global specrails-core framework, provider CLI/MCP/agent-chat issues, rails/jobs/specs, or GitHub/PR/cost/plugin questions. ' +
|
|
33
|
+
'Actions: triage (read-only support playbook + diagnostics), core_update_status/check/apply (global specrails-core framework update). ' +
|
|
34
|
+
'This tool never creates specs; repair actions require the normal permission tier and explicit user confirmation.',
|
|
35
|
+
tier: (a) => (String(a.action) === 'core_update_apply' ? 'ai-spawn' : 'read'),
|
|
36
|
+
hintTier: 'read',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
action: zod_1.z
|
|
39
|
+
.enum([
|
|
40
|
+
'triage',
|
|
41
|
+
'core_update_status',
|
|
42
|
+
'core_update_check',
|
|
43
|
+
'core_update_apply',
|
|
44
|
+
])
|
|
45
|
+
.describe('Support operation to perform'),
|
|
46
|
+
question: zod_1.z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('The user-facing problem or question to triage, in the user wording when possible'),
|
|
50
|
+
topic: zod_1.z
|
|
51
|
+
.enum(SUPPORT_TOPICS)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Optional explicit support topic; omit to infer from question'),
|
|
54
|
+
projectId: zod_1.z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Optional project id when the issue is project-specific; defaults to the active project when selected'),
|
|
58
|
+
version: zod_1.z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('core_update_apply: optional target specrails-core version; omit to use the latest version found by core_update_check'),
|
|
62
|
+
includeDiagnostics: zod_1.z
|
|
63
|
+
.boolean()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('When true or omitted, include read-only local diagnostics such as prerequisites, providers, settings, and projects. Project setup checkpoints are skipped for specrails-core installation triage because they are not a core health signal.'),
|
|
66
|
+
},
|
|
67
|
+
async handler(ctx, args) {
|
|
68
|
+
const action = String(args.action);
|
|
69
|
+
switch (action) {
|
|
70
|
+
case 'core_update_status':
|
|
71
|
+
return (0, types_1.apiCall)(ctx, 'GET', '/core-update/status');
|
|
72
|
+
case 'core_update_check':
|
|
73
|
+
return (0, types_1.apiCall)(ctx, 'POST', '/core-update/check', {});
|
|
74
|
+
case 'core_update_apply': {
|
|
75
|
+
const body = typeof args.version === 'string' && args.version.trim()
|
|
76
|
+
? { version: args.version.trim() }
|
|
77
|
+
: {};
|
|
78
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', '/core-update/update', body);
|
|
79
|
+
return {
|
|
80
|
+
...(typeof r === 'object' && r !== null ? r : { result: r }),
|
|
81
|
+
hint: 'Core update is async and app-global. Progress streams as core_update.progress / framework.updated. This updates the global specrails-core framework; it is not a per-project setup/install action.',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
case 'triage':
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Unknown action "${action}".`);
|
|
88
|
+
}
|
|
89
|
+
const question = typeof args.question === 'string' ? args.question.trim() : '';
|
|
90
|
+
const topic = isSupportTopic(args.topic) ? args.topic : inferTopic(question);
|
|
91
|
+
const includeDiagnostics = args.includeDiagnostics !== false;
|
|
92
|
+
const diagnostics = includeDiagnostics
|
|
93
|
+
? await collectDiagnostics(ctx, args.projectId, topic)
|
|
94
|
+
: { skipped: true };
|
|
95
|
+
return {
|
|
96
|
+
mode: 'support-triage',
|
|
97
|
+
doNotCreateSpec: true,
|
|
98
|
+
topic,
|
|
99
|
+
question: question || null,
|
|
100
|
+
supportPrompt: [
|
|
101
|
+
'Treat this as a help/support conversation, not product backlog intake.',
|
|
102
|
+
'Answer in the user language and solve the immediate setup or usage problem with concrete steps.',
|
|
103
|
+
'Do not create or propose a spec unless the user explicitly pivots from troubleshooting to new product work.',
|
|
104
|
+
'Prefer read-only diagnostics first. Ask at most two targeted questions if the next step depends on missing local context.',
|
|
105
|
+
'If a fix requires write, ai-spawn, or destructive permissions, explain the exact action and ask for confirmation before calling another tool.',
|
|
106
|
+
'For missing agents, skills, slash commands, or specrails-core framework files during a job, explain that these definitions live in the app-global specrails-core framework. Do not use project setup checkpoints as evidence of core health or as a reason to run project setup.',
|
|
107
|
+
],
|
|
108
|
+
localDiagnostics: diagnostics,
|
|
109
|
+
recommendedNextSteps: nextSteps(topic),
|
|
110
|
+
suggestedReadTools: suggestedTools(topic),
|
|
111
|
+
availableRepairActions: repairActions(topic),
|
|
112
|
+
responseChecklist: [
|
|
113
|
+
'State what the diagnostics show, including any unknowns or failed probes.',
|
|
114
|
+
'Give the smallest next action the user can take in the app or terminal.',
|
|
115
|
+
'Name the exact Specrails surface when relevant: Add Project, Setup, Settings > MCP, Agent Chat, Rails board, Job Detail, Analytics, Integrations.',
|
|
116
|
+
'If the issue is already resolved by state, say so and explain how to verify it.',
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
function isSupportTopic(value) {
|
|
124
|
+
return typeof value === 'string' && SUPPORT_TOPICS.includes(value);
|
|
125
|
+
}
|
|
126
|
+
function inferTopic(question) {
|
|
127
|
+
const q = question.toLowerCase();
|
|
128
|
+
if (/\b(mcp|claude desktop|cursor|external ai|token|settings.*mcp)\b/.test(q))
|
|
129
|
+
return 'mcp_connection';
|
|
130
|
+
if (/\b(agent chat|chat panel|shift\+tab|permission level|observe|operate|autonomous)\b/.test(q))
|
|
131
|
+
return 'agent_chat';
|
|
132
|
+
if (/\b(specrails-core|core framework|missing agents?|missing skills?|no agents?|no skills?|slash command|command not found|propose-feature|opsx|framework files?|reinstall)\b/.test(q))
|
|
133
|
+
return 'framework_install';
|
|
134
|
+
if (/\b(claude|codex|gemini|provider|engine|cli|auth|login|api key|path)\b/.test(q))
|
|
135
|
+
return 'provider_cli';
|
|
136
|
+
if (/\b(add project|project setup|setup wizard|provision|checkpoint|install config)\b/.test(q))
|
|
137
|
+
return 'project_setup';
|
|
138
|
+
if (/\b(install|installation|setup|prerequisite|node|npm|npx|git)\b/.test(q))
|
|
139
|
+
return 'installation';
|
|
140
|
+
if (/\b(rail|job|launch|run|queue|stuck|failed|logs?)\b/.test(q))
|
|
141
|
+
return 'rails_jobs';
|
|
142
|
+
if (/\b(github|git|pr|pull request|remote|branch|merge|integrate locally)\b/.test(q))
|
|
143
|
+
return 'git_pr';
|
|
144
|
+
if (/\b(cost|spend|budget|analytics|tokens?)\b/.test(q))
|
|
145
|
+
return 'analytics_costs';
|
|
146
|
+
if (/\b(plugin|integration|serena|marketplace)\b/.test(q))
|
|
147
|
+
return 'plugins_integrations';
|
|
148
|
+
if (/\b(spec|ticket|backlog|draft|todo|done|review)\b/.test(q))
|
|
149
|
+
return 'specs_backlog';
|
|
150
|
+
return 'general_usage';
|
|
151
|
+
}
|
|
152
|
+
async function collectDiagnostics(ctx, projectId, topic) {
|
|
153
|
+
const projects = ctx.registry.listContexts().map((c) => (0, projects_1.serializeProject)(c.project));
|
|
154
|
+
const selectedProjectId = projectId ?? (0, types_1.getActiveProject)(ctx) ?? undefined;
|
|
155
|
+
const out = {
|
|
156
|
+
mcp: {
|
|
157
|
+
enabled: (0, mcp_tiers_1.isMcpEnabled)(ctx.desktopDb),
|
|
158
|
+
tierWrite: (0, mcp_tiers_1.isTierEnabled)(ctx.desktopDb, 'write'),
|
|
159
|
+
tierAiSpawn: (0, mcp_tiers_1.isTierEnabled)(ctx.desktopDb, 'ai-spawn'),
|
|
160
|
+
tierDestructive: (0, mcp_tiers_1.isTierEnabled)(ctx.desktopDb, 'destructive'),
|
|
161
|
+
},
|
|
162
|
+
activeProjectId: (0, types_1.getActiveProject)(ctx),
|
|
163
|
+
projectCount: projects.length,
|
|
164
|
+
projects,
|
|
165
|
+
prerequisites: await safeDiagnostic(() => (0, types_1.apiCall)(ctx, 'GET', '/setup-prerequisites?diagnostic=1')),
|
|
166
|
+
availableProviders: await safeDiagnostic(() => (0, types_1.apiCall)(ctx, 'GET', '/available-providers')),
|
|
167
|
+
coreUpdate: await safeDiagnostic(() => (0, types_1.apiCall)(ctx, 'GET', '/core-update/status')),
|
|
168
|
+
};
|
|
169
|
+
if (!selectedProjectId) {
|
|
170
|
+
out.project = {
|
|
171
|
+
ok: false,
|
|
172
|
+
error: 'No active project or projectId was provided. Project-specific diagnostics need specrails_select_project or projectId.',
|
|
173
|
+
};
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
const project = safeLocal(() => (0, projects_1.serializeProject)((0, types_1.requireProject)(ctx, selectedProjectId).project));
|
|
177
|
+
out.project = project;
|
|
178
|
+
if (project.ok && topic !== 'framework_install') {
|
|
179
|
+
out.setupCheckpoints = await safeDiagnostic(() => (0, types_1.apiCall)(ctx, 'GET', `/projects/${selectedProjectId}/setup/checkpoints`));
|
|
180
|
+
}
|
|
181
|
+
else if (topic === 'framework_install') {
|
|
182
|
+
out.setupCheckpoints = {
|
|
183
|
+
skipped: true,
|
|
184
|
+
reason: 'Skipped intentionally: project setup checkpoints are not a specrails-core health check. Pending checkpoints or 0 agent/command counts must not be used to diagnose global core installation.',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
async function safeDiagnostic(fn) {
|
|
190
|
+
try {
|
|
191
|
+
return { ok: true, data: await fn() };
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function safeLocal(fn) {
|
|
198
|
+
try {
|
|
199
|
+
return { ok: true, data: fn() };
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function nextSteps(topic) {
|
|
206
|
+
switch (topic) {
|
|
207
|
+
case 'installation':
|
|
208
|
+
return [
|
|
209
|
+
'Read prerequisites and availableProviders from localDiagnostics.',
|
|
210
|
+
'If Node/npm/npx/git are missing, tell the user to install them, restart Specrails, then recheck.',
|
|
211
|
+
'If a provider CLI is missing, install and authenticate that CLI before retrying project setup.',
|
|
212
|
+
];
|
|
213
|
+
case 'project_setup':
|
|
214
|
+
return [
|
|
215
|
+
'Check whether the project is registered and whether setupCheckpoints.isInstalling is still true.',
|
|
216
|
+
'If setup is running, tell the user to wait or poll checkpoints; if failed, surface the last log lines and retry only the failed setup step.',
|
|
217
|
+
'If no project is selected, ask for the repo path and provider choices before using specrails_setup.',
|
|
218
|
+
];
|
|
219
|
+
case 'provider_cli':
|
|
220
|
+
return [
|
|
221
|
+
'Compare availableProviders with the providers installed on the project.',
|
|
222
|
+
'Ask the user to verify the provider CLI in a fresh terminal only when the local probe is missing or ambiguous.',
|
|
223
|
+
'For auth failures, direct them to the provider CLI login/API-key flow, then re-open or retry Specrails setup.',
|
|
224
|
+
];
|
|
225
|
+
case 'mcp_connection':
|
|
226
|
+
return [
|
|
227
|
+
'Check localDiagnostics.mcp.enabled and explain that external clients also need the Settings > MCP config and token.',
|
|
228
|
+
'If MCP is off, tell the user to enable it in Settings > MCP; do not try to mutate the MCP enable flag through this tool.',
|
|
229
|
+
'If a tier blocks an action, name the exact tier to enable in Settings > MCP.',
|
|
230
|
+
];
|
|
231
|
+
case 'agent_chat':
|
|
232
|
+
return [
|
|
233
|
+
'Explain the in-app permission ladder: observe, edit, operate, autonomous.',
|
|
234
|
+
'If a tool was refused, tell the user which level to switch to with Shift+Tab.',
|
|
235
|
+
'If Agent Chat cannot operate, check MCP enabled state and installed provider state first.',
|
|
236
|
+
];
|
|
237
|
+
case 'framework_install':
|
|
238
|
+
return [
|
|
239
|
+
'For specrails-core installation questions, evaluate only global core status and prerequisites. Do not treat project setup checkpoints as evidence of a core problem.',
|
|
240
|
+
'If global core status is current/healthy and the user has not provided an actual job error, say the core installation looks healthy and ask for the concrete failing job/error before recommending repair.',
|
|
241
|
+
'If a job explicitly reports missing agents, missing skills, unresolved slash commands, or missing /specrails:* commands, explain that those definitions come from the app-global specrails-core framework.',
|
|
242
|
+
'If an update is available, offer core_update_check followed by core_update_apply with confirmation. If no update is available but the job still fails with missing core definitions, give the manual fallback: run `npx specrails-core@latest update` from the project root, then retry, or collect the job diagnostic for a framework-loading bug.',
|
|
243
|
+
'Never recommend specrails_setup(install) or a project reinstall solely because setup/checkpoints show pending or 0 agents/commands.',
|
|
244
|
+
];
|
|
245
|
+
case 'rails_jobs':
|
|
246
|
+
return [
|
|
247
|
+
'Use read tools for rails/jobs status before guessing: specrails_rails, specrails_jobs, and specrails_watch only when the user asks to wait.',
|
|
248
|
+
'For a failed or stuck job, inspect the Job Detail logs and report the real failure.',
|
|
249
|
+
'Do not relaunch or stop anything without confirmation and the required permission level.',
|
|
250
|
+
];
|
|
251
|
+
case 'git_pr':
|
|
252
|
+
return [
|
|
253
|
+
'Use specrails_git read-only diagnostics for remote, status, branch, gh_auth, gh_repo, and PR questions.',
|
|
254
|
+
'Explain that PR creation/integration belongs to the rail PR-decision surface, not this support tool.',
|
|
255
|
+
'If there is no GitHub remote, explain local-only delivery and Integrate locally.',
|
|
256
|
+
];
|
|
257
|
+
case 'analytics_costs':
|
|
258
|
+
return [
|
|
259
|
+
'Use specrails_analytics(spending) for actual cost questions.',
|
|
260
|
+
'Distinguish app-wide budget settings from per-project analytics budget caps.',
|
|
261
|
+
'Prefix cost with ~ when the provider reports estimates.',
|
|
262
|
+
];
|
|
263
|
+
case 'plugins_integrations':
|
|
264
|
+
return [
|
|
265
|
+
'Use specrails_plugins read actions before suggesting install/uninstall.',
|
|
266
|
+
'For degraded plugins, report health details and avoid blocking rail launches.',
|
|
267
|
+
'Mutating plugin actions need explicit confirmation and the matching permission level.',
|
|
268
|
+
];
|
|
269
|
+
case 'specs_backlog':
|
|
270
|
+
return [
|
|
271
|
+
'Use specrails_specs list/get to explain current backlog state.',
|
|
272
|
+
'Only create or edit specs if the user asks for backlog work, not when they are asking how Specrails works.',
|
|
273
|
+
'Do not patch pipeline-managed statuses unless the user explicitly asks and understands the consequence.',
|
|
274
|
+
];
|
|
275
|
+
case 'general_usage':
|
|
276
|
+
return [
|
|
277
|
+
'Use specrails_guide and specrails_search to find the relevant domain.',
|
|
278
|
+
'Explain the UI path first, then offer to perform safe read/write actions from chat when appropriate.',
|
|
279
|
+
'Keep the answer short and operational; ask one targeted question if the requested surface is unclear.',
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function suggestedTools(topic) {
|
|
284
|
+
switch (topic) {
|
|
285
|
+
case 'installation':
|
|
286
|
+
return ['specrails_support', 'specrails_setup(prerequisites)', 'specrails_setup(available_providers)'];
|
|
287
|
+
case 'project_setup':
|
|
288
|
+
return ['specrails_projects(list|get)', 'specrails_setup(checkpoints)', 'specrails_setup(prerequisites)'];
|
|
289
|
+
case 'provider_cli':
|
|
290
|
+
return ['specrails_setup(available_providers)', 'specrails_setup(prerequisites)', 'specrails_projects(get)'];
|
|
291
|
+
case 'mcp_connection':
|
|
292
|
+
return ['specrails_settings(get)', 'specrails_guide', 'specrails_describe'];
|
|
293
|
+
case 'agent_chat':
|
|
294
|
+
return ['specrails_settings(get)', 'specrails_projects(list|get)', 'specrails_guide'];
|
|
295
|
+
case 'framework_install':
|
|
296
|
+
return ['specrails_support(triage|core_update_status|core_update_check)', 'specrails_jobs(get)', 'specrails_jobs(diagnostic)'];
|
|
297
|
+
case 'rails_jobs':
|
|
298
|
+
return ['specrails_rails', 'specrails_jobs', 'specrails_watch'];
|
|
299
|
+
case 'git_pr':
|
|
300
|
+
return ['specrails_git(remote|status|branch|gh_auth|gh_repo|gh_pr_list|gh_pr_view)'];
|
|
301
|
+
case 'analytics_costs':
|
|
302
|
+
return ['specrails_analytics(spending)', 'specrails_settings(get)'];
|
|
303
|
+
case 'plugins_integrations':
|
|
304
|
+
return ['specrails_plugins(list|health|preview)', 'specrails_projects(get)'];
|
|
305
|
+
case 'specs_backlog':
|
|
306
|
+
return ['specrails_specs(list|get)', 'specrails_guide'];
|
|
307
|
+
case 'general_usage':
|
|
308
|
+
return ['specrails_guide', 'specrails_search', 'specrails_describe'];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function repairActions(topic) {
|
|
312
|
+
if (topic === 'framework_install') {
|
|
313
|
+
return [
|
|
314
|
+
'specrails_support(core_update_check) then specrails_support(core_update_apply) - apply requires operate/ai-spawn and only makes sense when a newer global specrails-core framework is available.',
|
|
315
|
+
'Manual fallback when no update is available but a real job still cannot load core definitions: run `npx specrails-core@latest update` from the project root, then retry or collect a job diagnostic.',
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
if (topic !== 'project_setup' && topic !== 'installation')
|
|
319
|
+
return [];
|
|
320
|
+
return [
|
|
321
|
+
'specrails_setup(install, projectId) - operate/ai-spawn: use only for actual project setup/provisioning, not for specrails-core global installation questions.',
|
|
322
|
+
'specrails_support(core_update_check) then specrails_support(core_update_apply) - apply requires operate/ai-spawn: updates the app-global specrails-core framework when a newer version is available.',
|
|
323
|
+
];
|
|
324
|
+
}
|
|
@@ -193,7 +193,7 @@ function createMobileRouter(deps) {
|
|
|
193
193
|
if (typeof b.aiEngine === 'string')
|
|
194
194
|
narrowed.aiEngine = b.aiEngine;
|
|
195
195
|
if (typeof b.model === 'string')
|
|
196
|
-
narrowed.model = b.model; //
|
|
196
|
+
narrowed.model = b.model; // freestyle model
|
|
197
197
|
// rails-as-loops: let a mobile client launch a chosen Loop (factory or custom).
|
|
198
198
|
// Additive — the frozen bare-`mode` path is untouched (a v1 client sends no loopId).
|
|
199
199
|
if (typeof b.loopId === 'string')
|
|
@@ -148,7 +148,7 @@ function registerJobsRoutes(deps) {
|
|
|
148
148
|
// ─── Interactive jobs (manager-agnostic) ───────────────────────────────────
|
|
149
149
|
// Send one more user prompt to a running interactive job (queued behind the
|
|
150
150
|
// active turn — see InteractiveJobSession). The job row may be owned by
|
|
151
|
-
// EITHER manager: QueueManager (rail/
|
|
151
|
+
// EITHER manager: QueueManager (rail/freestyle/spawned jobs) or
|
|
152
152
|
// LoopRunManager (a loop run's job row, addressed by its ACTIVE step
|
|
153
153
|
// session). Try the queue first, then the loop engine. 202 = accepted;
|
|
154
154
|
// 409 = neither manager has an active interactive session for the id
|
|
@@ -64,7 +64,7 @@ function registerLoopRunRoutes(deps) {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
const provider = check.provider;
|
|
67
|
-
// claude-only guard (e.g. a loop that uses {{cmd:
|
|
67
|
+
// claude-only guard (e.g. a loop that uses {{cmd:freestyle}}).
|
|
68
68
|
const promptsText = loop.graph.nodes
|
|
69
69
|
.filter((n) => n.type === 'ai-step')
|
|
70
70
|
.map((n) => String(n.data?.prompt ?? ''))
|
|
@@ -76,7 +76,7 @@ function registerSettingsRoutes(deps) {
|
|
|
76
76
|
res.json({ scope: merged });
|
|
77
77
|
});
|
|
78
78
|
router.patch('/:projectId/settings', (req, res) => {
|
|
79
|
-
const { pipelineTelemetryEnabled, orchestratorModel, prePrompt,
|
|
79
|
+
const { pipelineTelemetryEnabled, orchestratorModel, prePrompt, freestylePrePrompt } = req.body ?? {};
|
|
80
80
|
const patch = {};
|
|
81
81
|
if (pipelineTelemetryEnabled !== undefined) {
|
|
82
82
|
patch.pipelineTelemetryEnabled = Boolean(pipelineTelemetryEnabled);
|
|
@@ -96,12 +96,12 @@ function registerSettingsRoutes(deps) {
|
|
|
96
96
|
}
|
|
97
97
|
patch.prePrompt = prePrompt;
|
|
98
98
|
}
|
|
99
|
-
if (
|
|
100
|
-
if (typeof
|
|
101
|
-
res.status(400).json({ error: '
|
|
99
|
+
if (freestylePrePrompt !== undefined) {
|
|
100
|
+
if (typeof freestylePrePrompt !== 'string') {
|
|
101
|
+
res.status(400).json({ error: 'freestylePrePrompt must be a string' });
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
-
patch.
|
|
104
|
+
patch.freestylePrePrompt = freestylePrePrompt;
|
|
105
105
|
}
|
|
106
106
|
if (req.body?.integrationBranch !== undefined) {
|
|
107
107
|
const ib = req.body.integrationBranch;
|