tide-commander 1.32.2 → 1.35.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.
Files changed (80) hide show
  1. package/dist/assets/{BossLogsModal-2lqGyuCD.js → BossLogsModal-B_dgVF7L.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-CR-ofDeB.js → BossSpawnModal-CSO1bYxA.js} +1 -1
  3. package/dist/assets/{ControlsModal-DQ8QVshA.js → ControlsModal-WMTTqbca.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-D6yQTHy7.js → DockerLogsModal-THzhLHch.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-wechGxGl.js → EmbeddedEditor-DWLKJYav.js} +1 -1
  6. package/dist/assets/GmailOAuthSetup-DN9ceaS6.js +270 -0
  7. package/dist/assets/{GoogleOAuthSetup-BcpJEydM.js → GoogleOAuthSetup-bVST2EOB.js} +1 -1
  8. package/dist/assets/{IframeModal-E2E7NR08.js → IframeModal-BELsjvgi.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-CrS2QOFR.js → IntegrationsPanel-DwDr4BRt.js} +2 -2
  10. package/dist/assets/{LogViewerModal-BpIPZeFr.js → LogViewerModal-CMe04PO5.js} +1 -1
  11. package/dist/assets/MonitoringModal-CqSalNeY.js +1 -0
  12. package/dist/assets/{PM2LogsModal-CDk_2mi1.js → PM2LogsModal-CCmCDxVt.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-BmgBrk9J.js → RestoreArchivedAreaModal-IfzPidIv.js} +1 -1
  14. package/dist/assets/{SaveSnapshotModal-DWta9pcx.js → SaveSnapshotModal-DUhrVD5l.js} +1 -1
  15. package/dist/assets/{Scene2DCanvas-C3CcFsjU.js → Scene2DCanvas-Bl5DUC7w.js} +1 -1
  16. package/dist/assets/{SceneManager-mFUakRNl.js → SceneManager-BGO9tiaI.js} +1 -1
  17. package/dist/assets/{SkillsPanel-Br3h8GNh.js → SkillsPanel-CPFOI4Tl.js} +1 -1
  18. package/dist/assets/{SnapshotManager-CPYWfnPR.js → SnapshotManager-Cbu0tJBz.js} +1 -1
  19. package/dist/assets/{SpawnModal-DSDirR0j.js → SpawnModal-BqDbsYLY.js} +1 -1
  20. package/dist/assets/{SubordinateAssignmentModal-Daz67phV.js → SubordinateAssignmentModal-DOqkhL_L.js} +1 -1
  21. package/dist/assets/{SupervisorPanel-DoAl5e8W.js → SupervisorPanel-BvX-dlk_.js} +1 -1
  22. package/dist/assets/{TriggerManagerPanel-CVIHbqDt.js → TriggerManagerPanel-RUVFmKmf.js} +1 -1
  23. package/dist/assets/WorkflowEditorPanel-CwZpEqzM.js +42 -0
  24. package/dist/assets/browser-ponyfill-DZOWXZ4K.js +2 -0
  25. package/dist/assets/camera-D_KeL_pz.js +1 -0
  26. package/dist/assets/{index-I-I3pPPW.js → index-B-wV06cR.js} +1 -1
  27. package/dist/assets/index-BFguOWBW.js +2 -0
  28. package/dist/assets/{index-uXOqPsuU.js → index-C7gqY2AA.js} +1 -1
  29. package/dist/assets/{index-CE_GbjZ6.js → index-CiD1Rwaq.js} +1 -1
  30. package/dist/assets/index-D4nfDvz4.js +49 -0
  31. package/dist/assets/{index-CvMf5n2v.js → index-DDPUtz8-.js} +1 -1
  32. package/dist/assets/{index-Chrxgrys.js → index-EH8IBvSU.js} +1 -1
  33. package/dist/assets/{index-DWVQ48nQ.js → index-H0PzHVFw.js} +1 -1
  34. package/dist/assets/main-Cjm0d8dZ.js +152 -0
  35. package/dist/assets/main-DqC9_fF4.css +1 -0
  36. package/dist/assets/{prism-cpp-CcQnz8LL.js → prism-cpp-CK2Ly5dS.js} +1 -1
  37. package/dist/assets/{prism-csharp-DFIAaw4Y.js → prism-csharp-ByDDDiWW.js} +1 -1
  38. package/dist/assets/{prism-elixir-jP4m4T-8.js → prism-elixir-df27OMMQ.js} +1 -1
  39. package/dist/assets/{prism-haskell-BrMZM7_F.js → prism-haskell-Ce8aBmia.js} +1 -1
  40. package/dist/assets/{prism-java-BEsh8u4L.js → prism-java-CK6tws4L.js} +1 -1
  41. package/dist/assets/{prism-perl-ecHKp0bZ.js → prism-perl-UZfqnD51.js} +1 -1
  42. package/dist/assets/{prism-php-Ch-kk89U.js → prism-php-Dt9698bA.js} +1 -1
  43. package/dist/assets/{prism-ruby-zNpGDk6v.js → prism-ruby-CQBUuZIF.js} +1 -1
  44. package/dist/assets/{prism-scss-BeuXx0O0.js → prism-scss-CeN16CFC.js} +1 -1
  45. package/dist/assets/{vendor-react-uS-d4TUT.js → vendor-react--Eh9ivFN.js} +2 -2
  46. package/dist/assets/{web-IWJRtE3-.js → web-D1vWYL8u.js} +1 -1
  47. package/dist/assets/{web-DdRt5c0R.js → web-DUq3Undh.js} +1 -1
  48. package/dist/index.html +3 -3
  49. package/dist/src/packages/server/data/builtin-skills/boss-instructions.js +57 -66
  50. package/dist/src/packages/server/data/builtin-skills/index.js +2 -0
  51. package/dist/src/packages/server/data/builtin-skills/workflow-builder.js +253 -0
  52. package/dist/src/packages/server/data/builtin-skills/workflow-designer.js +157 -0
  53. package/dist/src/packages/server/data/event-queries.js +24 -0
  54. package/dist/src/packages/server/data/migrations/002_workflow_agent_binding.sql +14 -0
  55. package/dist/src/packages/server/data/migrations/003_matcher_executions.sql +19 -0
  56. package/dist/src/packages/server/data/migrations/004_matcher_message_source.sql +8 -0
  57. package/dist/src/packages/server/integrations/gmail/gmail-client.js +75 -15
  58. package/dist/src/packages/server/integrations/gmail/gmail-config.js +32 -2
  59. package/dist/src/packages/server/integrations/gmail/gmail-routes.js +5 -0
  60. package/dist/src/packages/server/integrations/gmail/index.js +23 -1
  61. package/dist/src/packages/server/integrations/jira/jira-client.js +11 -5
  62. package/dist/src/packages/server/integrations/jira/jira-routes.js +20 -3
  63. package/dist/src/packages/server/integrations/jira/jira-skill.js +110 -58
  64. package/dist/src/packages/server/routes/trigger-routes.js +22 -0
  65. package/dist/src/packages/server/routes/workflow-routes.js +86 -2
  66. package/dist/src/packages/server/services/boss-message-service.js +1 -1
  67. package/dist/src/packages/server/services/llm-matcher-service.js +50 -81
  68. package/dist/src/packages/server/services/trigger-service.js +195 -6
  69. package/dist/src/packages/server/services/workflow-executor.js +230 -0
  70. package/dist/src/packages/server/services/workflow-service.js +59 -13
  71. package/package.json +7 -7
  72. package/dist/assets/GmailOAuthSetup-B2GDjROU.js +0 -222
  73. package/dist/assets/MonitoringModal-CIF9MUm9.js +0 -1
  74. package/dist/assets/WorkflowEditorPanel-C4BRfmDM.js +0 -42
  75. package/dist/assets/browser-ponyfill-DIm4hKhx.js +0 -2
  76. package/dist/assets/camera-8crtHeRa.js +0 -1
  77. package/dist/assets/index--PCy0J0f.js +0 -49
  78. package/dist/assets/index-DOzx4Y9b.js +0 -2
  79. package/dist/assets/main-DQpuQfqS.css +0 -1
  80. package/dist/assets/main-DahZb6P4.js +0 -152
@@ -14,6 +14,7 @@ const REDIRECT_PATH = '/api/email/auth/callback';
14
14
  // ─── State ───
15
15
  let ctx = null;
16
16
  let config = {
17
+ authMethod: 'oauth2',
17
18
  clientId: '',
18
19
  clientSecret: '',
19
20
  pollingIntervalMs: 30000,
@@ -31,25 +32,63 @@ let lastHistoryId;
31
32
  export async function init(context) {
32
33
  ctx = context;
33
34
  loadConfig();
34
- if (!config.clientId || !config.clientSecret) {
35
- ctx.log.info('Gmail not configured (missing OAuth credentials)');
36
- return;
37
- }
38
- oauth2Client = new google.auth.OAuth2(config.clientId, config.clientSecret, `${ctx.serverConfig.baseUrl}${REDIRECT_PATH}`);
39
- // Load refresh token from secrets
40
- const refreshToken = ctx.secrets.get('GOOGLE_REFRESH_TOKEN') || config.refreshToken;
41
- if (refreshToken) {
42
- oauth2Client.setCredentials({ refresh_token: refreshToken });
43
- gmail = google.gmail({ version: 'v1', auth: oauth2Client });
35
+ // Reset state on reinit
36
+ gmail = null;
37
+ oauth2Client = null;
38
+ authenticatedEmail = undefined;
39
+ lastError = undefined;
40
+ const authMethod = config.authMethod || 'oauth2';
41
+ if (authMethod === 'service_account') {
42
+ // ── Service Account authentication via domain-wide delegation ──
43
+ if (!config.serviceAccountJson || !config.impersonateEmail) {
44
+ ctx.log.info('Gmail not configured (missing service account credentials)');
45
+ return;
46
+ }
44
47
  try {
48
+ const sa = JSON.parse(config.serviceAccountJson);
49
+ const jwtClient = new google.auth.JWT({
50
+ email: sa.client_email,
51
+ key: sa.private_key,
52
+ scopes: SCOPES,
53
+ subject: config.impersonateEmail,
54
+ });
55
+ gmail = google.gmail({ version: 'v1', auth: jwtClient });
45
56
  const profile = await gmail.users.getProfile({ userId: 'me' });
46
57
  authenticatedEmail = profile.data.emailAddress ?? undefined;
47
58
  lastHistoryId = profile.data.historyId ?? undefined;
48
- ctx.log.info(`Gmail authenticated as ${authenticatedEmail}`);
59
+ lastError = undefined;
60
+ ctx.log.info(`Gmail authenticated via service account as ${authenticatedEmail}`);
61
+ startPolling();
49
62
  }
50
63
  catch (err) {
51
- lastError = `Authentication failed: ${err}`;
52
- ctx.log.error('Gmail authentication failed', err);
64
+ lastError = `Service account authentication failed: ${err}`;
65
+ ctx.log.error('Gmail service account authentication failed', err);
66
+ }
67
+ }
68
+ else {
69
+ // ── OAuth2 authentication ──
70
+ if (!config.clientId || !config.clientSecret) {
71
+ ctx.log.info('Gmail not configured (missing OAuth credentials)');
72
+ return;
73
+ }
74
+ oauth2Client = new google.auth.OAuth2(config.clientId, config.clientSecret, `${ctx.serverConfig.baseUrl}${REDIRECT_PATH}`);
75
+ // Load refresh token from secrets
76
+ const refreshToken = ctx.secrets.get('GOOGLE_REFRESH_TOKEN') || config.refreshToken;
77
+ if (refreshToken) {
78
+ oauth2Client.setCredentials({ refresh_token: refreshToken });
79
+ gmail = google.gmail({ version: 'v1', auth: oauth2Client });
80
+ try {
81
+ const profile = await gmail.users.getProfile({ userId: 'me' });
82
+ authenticatedEmail = profile.data.emailAddress ?? undefined;
83
+ lastHistoryId = profile.data.historyId ?? undefined;
84
+ lastError = undefined;
85
+ ctx.log.info(`Gmail authenticated as ${authenticatedEmail}`);
86
+ startPolling();
87
+ }
88
+ catch (err) {
89
+ lastError = `Authentication failed: ${err}`;
90
+ ctx.log.error('Gmail authentication failed', err);
91
+ }
53
92
  }
54
93
  }
55
94
  }
@@ -59,14 +98,21 @@ function loadConfig() {
59
98
  const clientId = ctx.secrets.get('GOOGLE_CLIENT_ID') || '';
60
99
  const clientSecret = ctx.secrets.get('GOOGLE_CLIENT_SECRET') || '';
61
100
  const refreshToken = ctx.secrets.get('GOOGLE_REFRESH_TOKEN');
101
+ const serviceAccountJson = ctx.secrets.get('GOOGLE_SERVICE_ACCOUNT_JSON') || undefined;
102
+ const impersonateEmail = ctx.secrets.get('GOOGLE_IMPERSONATE_EMAIL') || undefined;
62
103
  config = {
63
104
  ...config,
64
105
  clientId,
65
106
  clientSecret,
66
107
  refreshToken: refreshToken || undefined,
108
+ serviceAccountJson,
109
+ impersonateEmail,
67
110
  };
68
111
  }
69
112
  export function updateConfig(updates) {
113
+ if (updates.authMethod !== undefined) {
114
+ config.authMethod = updates.authMethod;
115
+ }
70
116
  if (updates.clientId !== undefined) {
71
117
  config.clientId = updates.clientId;
72
118
  ctx?.secrets.set('GOOGLE_CLIENT_ID', updates.clientId);
@@ -75,6 +121,14 @@ export function updateConfig(updates) {
75
121
  config.clientSecret = updates.clientSecret;
76
122
  ctx?.secrets.set('GOOGLE_CLIENT_SECRET', updates.clientSecret);
77
123
  }
124
+ if (updates.serviceAccountJson !== undefined) {
125
+ config.serviceAccountJson = updates.serviceAccountJson;
126
+ ctx?.secrets.set('GOOGLE_SERVICE_ACCOUNT_JSON', updates.serviceAccountJson);
127
+ }
128
+ if (updates.impersonateEmail !== undefined) {
129
+ config.impersonateEmail = updates.impersonateEmail;
130
+ ctx?.secrets.set('GOOGLE_IMPERSONATE_EMAIL', updates.impersonateEmail);
131
+ }
78
132
  if (updates.pollingIntervalMs !== undefined) {
79
133
  config.pollingIntervalMs = updates.pollingIntervalMs;
80
134
  }
@@ -91,8 +145,10 @@ export function shutdown() {
91
145
  }
92
146
  // ─── Status ───
93
147
  export function getStatus() {
148
+ const oauthConfigured = Boolean(config.clientId && config.clientSecret);
149
+ const serviceAccountConfigured = Boolean(config.serviceAccountJson && config.impersonateEmail);
94
150
  return {
95
- configured: Boolean(config.clientId && config.clientSecret),
151
+ configured: oauthConfigured || serviceAccountConfigured,
96
152
  authenticated: Boolean(gmail && authenticatedEmail),
97
153
  emailAddress: authenticatedEmail,
98
154
  pollingActive: pollingTimer !== null,
@@ -102,7 +158,9 @@ export function getStatus() {
102
158
  };
103
159
  }
104
160
  export function isConfigured() {
105
- return Boolean(config.clientId && config.clientSecret);
161
+ const oauthConfigured = Boolean(config.clientId && config.clientSecret);
162
+ const serviceAccountConfigured = Boolean(config.serviceAccountJson && config.impersonateEmail);
163
+ return oauthConfigured || serviceAccountConfigured;
106
164
  }
107
165
  // ─── OAuth2 ───
108
166
  export function getAuthUrl() {
@@ -248,6 +306,7 @@ function parseGmailMessage(msg) {
248
306
  const subject = getHeader('Subject');
249
307
  const date = msg.internalDate ? parseInt(msg.internalDate, 10) : Date.now();
250
308
  const inReplyTo = getHeader('In-Reply-To') || undefined;
309
+ const rfc822MessageId = getHeader('Message-ID') || getHeader('Message-Id') || undefined;
251
310
  // Extract body
252
311
  let body = '';
253
312
  let bodyHtml = '';
@@ -285,6 +344,7 @@ function parseGmailMessage(msg) {
285
344
  return {
286
345
  messageId: msg.id || '',
287
346
  threadId: msg.threadId || '',
347
+ rfc822MessageId,
288
348
  from,
289
349
  to,
290
350
  cc,
@@ -3,12 +3,25 @@
3
3
  * ConfigField[] for OAuth credentials, polling interval, and approval defaults.
4
4
  */
5
5
  export const gmailConfigSchema = [
6
+ {
7
+ key: 'authMethod',
8
+ label: 'Authentication Method',
9
+ type: 'select',
10
+ description: 'Choose OAuth2 (browser login) or Service Account (domain-wide delegation)',
11
+ required: false,
12
+ defaultValue: 'oauth2',
13
+ options: [
14
+ { label: 'OAuth2', value: 'oauth2' },
15
+ { label: 'Service Account', value: 'service_account' },
16
+ ],
17
+ group: 'Authentication',
18
+ },
6
19
  {
7
20
  key: 'clientId',
8
21
  label: 'Google OAuth Client ID',
9
22
  type: 'text',
10
23
  description: 'OAuth2 client ID from Google Cloud Console',
11
- required: true,
24
+ required: false,
12
25
  secret: true,
13
26
  group: 'Authentication',
14
27
  },
@@ -17,10 +30,27 @@ export const gmailConfigSchema = [
17
30
  label: 'Google OAuth Client Secret',
18
31
  type: 'password',
19
32
  description: 'OAuth2 client secret from Google Cloud Console',
20
- required: true,
33
+ required: false,
34
+ secret: true,
35
+ group: 'Authentication',
36
+ },
37
+ {
38
+ key: 'serviceAccountJson',
39
+ label: 'Service Account JSON',
40
+ type: 'textarea',
41
+ description: 'Full JSON content of the Google service account key file',
42
+ required: false,
21
43
  secret: true,
22
44
  group: 'Authentication',
23
45
  },
46
+ {
47
+ key: 'impersonateEmail',
48
+ label: 'Impersonate Email',
49
+ type: 'email',
50
+ description: 'Email address to impersonate via domain-wide delegation (required for service account)',
51
+ required: false,
52
+ group: 'Authentication',
53
+ },
24
54
  {
25
55
  key: 'pollingIntervalMs',
26
56
  label: 'Polling Interval (ms)',
@@ -102,6 +102,11 @@ router.get('/status', (req, res) => {
102
102
  // GET /api/email/auth/url — Get OAuth authorization URL
103
103
  router.get('/auth/url', (req, res) => {
104
104
  try {
105
+ const currentConfig = gmailClient.getConfig();
106
+ if (currentConfig.authMethod === 'service_account') {
107
+ res.status(400).json({ error: 'OAuth flow is not used with service account authentication. Configure service account JSON and impersonate email instead.' });
108
+ return;
109
+ }
105
110
  const authUrl = gmailClient.getAuthUrl();
106
111
  res.json({ url: authUrl });
107
112
  }
@@ -12,7 +12,7 @@ let integrationCtx = null;
12
12
  export const gmailPlugin = {
13
13
  id: 'gmail',
14
14
  name: 'Gmail',
15
- description: 'Email sending, receiving, and approval checking via Gmail',
15
+ description: 'Send and receive emails through Gmail. Supports OAuth 2.0 and Service Account authentication.',
16
16
  routePrefix: '/email',
17
17
  async init(ctx) {
18
18
  integrationCtx = ctx;
@@ -39,15 +39,21 @@ export const gmailPlugin = {
39
39
  getConfig() {
40
40
  if (!integrationCtx) {
41
41
  return {
42
+ authMethod: 'oauth2',
42
43
  clientId: '',
43
44
  clientSecret: '',
45
+ serviceAccountJson: '',
46
+ impersonateEmail: '',
44
47
  pollingIntervalMs: 30000,
45
48
  defaultApprovalKeywords: 'approved,aprobado,autorizado,yes,ok',
46
49
  };
47
50
  }
48
51
  return {
52
+ authMethod: gmailClient.getConfig().authMethod || 'oauth2',
49
53
  clientId: integrationCtx.secrets.get('GOOGLE_CLIENT_ID') || '',
50
54
  clientSecret: integrationCtx.secrets.get('GOOGLE_CLIENT_SECRET') || '',
55
+ serviceAccountJson: integrationCtx.secrets.get('GOOGLE_SERVICE_ACCOUNT_JSON') || '',
56
+ impersonateEmail: integrationCtx.secrets.get('GOOGLE_IMPERSONATE_EMAIL') || '',
51
57
  pollingIntervalMs: gmailClient.getConfig().pollingIntervalMs,
52
58
  defaultApprovalKeywords: gmailClient.getConfig().defaultApprovalKeywords.join(','),
53
59
  };
@@ -56,6 +62,9 @@ export const gmailPlugin = {
56
62
  if (!integrationCtx)
57
63
  throw new Error('Gmail not initialized');
58
64
  const updates = {};
65
+ if (config.authMethod !== undefined) {
66
+ updates.authMethod = config.authMethod;
67
+ }
59
68
  if (config.clientId) {
60
69
  updates.clientId = config.clientId;
61
70
  integrationCtx.secrets.set('GOOGLE_CLIENT_ID', config.clientId);
@@ -64,6 +73,14 @@ export const gmailPlugin = {
64
73
  updates.clientSecret = config.clientSecret;
65
74
  integrationCtx.secrets.set('GOOGLE_CLIENT_SECRET', config.clientSecret);
66
75
  }
76
+ if (config.serviceAccountJson) {
77
+ updates.serviceAccountJson = config.serviceAccountJson;
78
+ integrationCtx.secrets.set('GOOGLE_SERVICE_ACCOUNT_JSON', config.serviceAccountJson);
79
+ }
80
+ if (config.impersonateEmail) {
81
+ updates.impersonateEmail = config.impersonateEmail;
82
+ integrationCtx.secrets.set('GOOGLE_IMPERSONATE_EMAIL', config.impersonateEmail);
83
+ }
67
84
  if (config.pollingIntervalMs) {
68
85
  updates.pollingIntervalMs = config.pollingIntervalMs;
69
86
  }
@@ -75,6 +92,11 @@ export const gmailPlugin = {
75
92
  updates.defaultApprovalKeywords = keywords;
76
93
  }
77
94
  gmailClient.updateConfig(updates);
95
+ // Re-initialize authentication when auth method or credentials change
96
+ if (config.authMethod !== undefined || config.serviceAccountJson || config.clientId) {
97
+ gmailClient.shutdown();
98
+ await gmailClient.init(integrationCtx);
99
+ }
78
100
  },
79
101
  getCustomSettingsComponent() {
80
102
  return 'gmail-oauth';
@@ -118,12 +118,18 @@ export class JiraClient {
118
118
  }
119
119
  // ─── Search ───
120
120
  async searchIssues(jql, opts) {
121
- const params = new URLSearchParams({
121
+ // Use POST /rest/api/3/search which returns full issue fields by default
122
+ // (the newer /search/jql endpoint only returns IDs unless fields are explicit)
123
+ const body = {
122
124
  jql,
123
- maxResults: String(opts?.maxResults ?? 25),
124
- startAt: String(opts?.startAt ?? 0),
125
- });
126
- return (await this.request('GET', `/rest/api/3/search/jql?${params}`));
125
+ maxResults: opts?.maxResults ?? 25,
126
+ startAt: opts?.startAt ?? 0,
127
+ fields: opts?.fields ?? [
128
+ 'summary', 'status', 'priority', 'assignee', 'issuetype',
129
+ 'project', 'created', 'updated', 'labels',
130
+ ],
131
+ };
132
+ return (await this.request('POST', '/rest/api/3/search', body));
127
133
  }
128
134
  // ─── Service Desk (optional) ───
129
135
  async createServiceRequest(serviceDeskId, requestTypeId, params) {
@@ -202,13 +202,30 @@ export function createJiraRoutes(client, ctx) {
202
202
  try {
203
203
  const jql = req.query.jql;
204
204
  if (!jql) {
205
- res.status(400).json({ error: 'jql query parameter is required' });
205
+ res.status(400).json({ error: 'jql query parameter is required. Jira Cloud requires bounded queries - always include a filter like project = X, created >= -30d, or similar.' });
206
206
  return;
207
207
  }
208
208
  const maxResults = parseInt(req.query.maxResults) || 25;
209
209
  const startAt = parseInt(req.query.startAt) || 0;
210
- const result = await client.searchIssues(jql, { maxResults, startAt });
211
- res.json(result);
210
+ const fieldsParam = req.query.fields;
211
+ const fields = fieldsParam ? fieldsParam.split(',').map(f => f.trim()) : undefined;
212
+ const result = await client.searchIssues(jql, { maxResults, startAt, fields });
213
+ // Return a clean, agent-friendly response with key fields inline
214
+ const issues = result.issues.map(issue => ({
215
+ key: issue.key,
216
+ id: issue.id,
217
+ summary: issue.fields.summary,
218
+ status: issue.fields.status?.name,
219
+ priority: issue.fields.priority?.name,
220
+ assignee: issue.fields.assignee?.displayName,
221
+ issueType: issue.fields.issuetype?.name,
222
+ project: issue.fields.project?.key,
223
+ created: issue.fields.created,
224
+ updated: issue.fields.updated,
225
+ labels: issue.fields.labels,
226
+ self: issue.self,
227
+ }));
228
+ res.json({ issues, total: result.total, startAt: result.startAt, maxResults: result.maxResults });
212
229
  }
213
230
  catch (err) {
214
231
  const message = err instanceof Error ? err.message : 'Search failed';
@@ -9,105 +9,157 @@ export const jiraSkill = {
9
9
  allowedTools: ['Bash(curl:*)'],
10
10
  content: `# Jira Service Desk
11
11
 
12
- You have access to the Jira Service Desk integration. Use these endpoints via curl.
12
+ Manage Jira issues through the proxy API. Auth headers are added automatically by the system.
13
+ All Jira credentials (base URL, email, API token) are handled server-side - never include them in requests.
13
14
 
14
- ## Create a ticket
15
+ ## Quick Reference
15
16
 
17
+ **Search recent issues:**
16
18
  \`\`\`bash
17
- curl -s -X POST "{{BASE_URL}}/api/jira/issues" \\
19
+ curl -s "http://localhost:5174/api/jira/search?jql=created%20%3E%3D%20-30d%20ORDER%20BY%20created%20DESC&maxResults=5"
20
+ \`\`\`
21
+
22
+ **Get a specific issue:**
23
+ \`\`\`bash
24
+ curl -s http://localhost:5174/api/jira/issues/SD-1234
25
+ \`\`\`
26
+
27
+ **Create an issue:**
28
+ \`\`\`bash
29
+ curl -s -X POST http://localhost:5174/api/jira/issues \\
18
30
  -H "Content-Type: application/json" \\
19
- -H "X-Auth-Token: {{AUTH_TOKEN}}" \\
20
- -d '{
21
- "projectKey": "SD",
22
- "issueType": "Change Request",
23
- "summary": "CC - Release v2.1.0 - 2026-03-20",
24
- "description": "Control de Cambios for release v2.1.0. Requested by: John Doe. Systems affected: API, Frontend.",
25
- "priority": "Medium",
26
- "labels": ["cc", "release"]
27
- }'
31
+ -d '{"summary":"Issue title","description":"Details here"}'
28
32
  \`\`\`
29
- Returns: \`{ "key": "SD-1234", "id": "10042", "self": "https://..." }\`
33
+ If projectKey and issueType are omitted, configured defaults are used.
30
34
 
31
- ## Get a ticket
35
+ ---
36
+
37
+ ## Search (JQL)
32
38
 
33
39
  \`\`\`bash
34
- curl -s "{{BASE_URL}}/api/jira/issues/SD-1234" \\
35
- -H "X-Auth-Token: {{AUTH_TOKEN}}"
40
+ curl -s "http://localhost:5174/api/jira/search?jql=QUERY&maxResults=N&startAt=0"
36
41
  \`\`\`
37
42
 
38
- ## Update a ticket
43
+ Returns flat issue objects with key, summary, status, priority, assignee, issueType, project, created, updated, and labels inline - no extra API calls needed.
44
+
45
+ Query params: \`jql\` (required), \`maxResults\` (default 25), \`startAt\` (default 0), \`fields\` (comma-separated, optional override)
46
+
47
+ **IMPORTANT: Jira Cloud rejects unbounded JQL.** Always include a filter like \`project = X\`, \`created >= -30d\`, or similar.
39
48
 
49
+ ### Common JQL Recipes
50
+
51
+ \`\`\`
52
+ # Last N issues across all projects
53
+ created >= -30d ORDER BY created DESC
54
+
55
+ # Issues by project
56
+ project = SD ORDER BY created DESC
57
+
58
+ # Open issues
59
+ project = SD AND status != Done ORDER BY updated DESC
60
+
61
+ # Issues assigned to someone
62
+ project = SD AND assignee = "user@email.com" ORDER BY created DESC
63
+
64
+ # Search by keyword in summary
65
+ project = SD AND summary ~ "vpn" ORDER BY created DESC
66
+
67
+ # Issues with specific label
68
+ project = SD AND labels = "release" ORDER BY created DESC
69
+
70
+ # Recently updated
71
+ updated >= -7d ORDER BY updated DESC
72
+ \`\`\`
73
+
74
+ Remember to URL-encode the jql value in the query string (spaces as %20, = as %3D, etc.), or use curl --data-urlencode:
40
75
  \`\`\`bash
41
- curl -s -X PATCH "{{BASE_URL}}/api/jira/issues/SD-1234" \\
42
- -H "Content-Type: application/json" \\
43
- -H "X-Auth-Token: {{AUTH_TOKEN}}" \\
44
- -d '{ "summary": "Updated summary", "priority": "High" }'
76
+ curl -s -G http://localhost:5174/api/jira/search --data-urlencode "jql=project = SD ORDER BY created DESC" --data-urlencode "maxResults=10"
45
77
  \`\`\`
46
78
 
47
- ## Add a comment
79
+ ## Get Issue
48
80
 
49
81
  \`\`\`bash
50
- curl -s -X POST "{{BASE_URL}}/api/jira/issues/SD-1234/comments" \\
82
+ curl -s http://localhost:5174/api/jira/issues/SD-1234
83
+ \`\`\`
84
+
85
+ Returns full Jira issue with all fields including description, comments count, etc.
86
+
87
+ ## Create Issue
88
+
89
+ \`\`\`bash
90
+ curl -s -X POST http://localhost:5174/api/jira/issues \\
51
91
  -H "Content-Type: application/json" \\
52
- -H "X-Auth-Token: {{AUTH_TOKEN}}" \\
53
- -d '{ "body": "CC document generated and sent for approval." }'
92
+ -d '{
93
+ "projectKey": "SD",
94
+ "issueType": "Task",
95
+ "summary": "Issue title",
96
+ "description": "Issue description",
97
+ "priority": "Medium",
98
+ "labels": ["tag1", "tag2"]
99
+ }'
54
100
  \`\`\`
55
101
 
56
- ## Transition a ticket (change status)
102
+ \`projectKey\` and \`issueType\` are optional if defaults are configured in the integration settings.
103
+ Returns: \`{ "key": "SD-1234", "id": "10042", "self": "..." }\`
104
+
105
+ ## Update Issue
57
106
 
58
- First, list available transitions:
59
107
  \`\`\`bash
60
- curl -s "{{BASE_URL}}/api/jira/issues/SD-1234/transitions" \\
61
- -H "X-Auth-Token: {{AUTH_TOKEN}}"
108
+ curl -s -X PATCH http://localhost:5174/api/jira/issues/SD-1234 \\
109
+ -H "Content-Type: application/json" \\
110
+ -d '{"summary": "Updated title", "priority": "High"}'
62
111
  \`\`\`
63
112
 
64
- Then transition:
113
+ ## Comments
114
+
115
+ **Add a comment:**
65
116
  \`\`\`bash
66
- curl -s -X POST "{{BASE_URL}}/api/jira/issues/SD-1234/transitions" \\
117
+ curl -s -X POST http://localhost:5174/api/jira/issues/SD-1234/comments \\
67
118
  -H "Content-Type: application/json" \\
68
- -H "X-Auth-Token: {{AUTH_TOKEN}}" \\
69
- -d '{ "transitionId": "31", "comment": "Approved and release completed" }'
119
+ -d '{"body": "Comment text here"}'
120
+ \`\`\`
121
+
122
+ **Get comments:**
123
+ \`\`\`bash
124
+ curl -s http://localhost:5174/api/jira/issues/SD-1234/comments
70
125
  \`\`\`
71
126
 
72
- ## Search tickets (JQL)
127
+ ## Transitions (Change Status)
128
+
129
+ **List available transitions:**
130
+ \`\`\`bash
131
+ curl -s http://localhost:5174/api/jira/issues/SD-1234/transitions
132
+ \`\`\`
73
133
 
134
+ **Apply a transition:**
74
135
  \`\`\`bash
75
- curl -s "{{BASE_URL}}/api/jira/search?jql=project%3DSD%20AND%20labels%3Dcc&maxResults=10" \\
76
- -H "X-Auth-Token: {{AUTH_TOKEN}}"
136
+ curl -s -X POST http://localhost:5174/api/jira/issues/SD-1234/transitions \\
137
+ -H "Content-Type: application/json" \\
138
+ -d '{"transitionId": "31", "comment": "Transition reason"}'
77
139
  \`\`\`
78
140
 
79
- ## Create a Service Desk request
141
+ ## Service Desk Requests
80
142
 
81
143
  \`\`\`bash
82
- curl -s -X POST "{{BASE_URL}}/api/jira/service-desk/1/requests" \\
144
+ curl -s -X POST http://localhost:5174/api/jira/service-desk/1/requests \\
83
145
  -H "Content-Type: application/json" \\
84
- -H "X-Auth-Token: {{AUTH_TOKEN}}" \\
85
- -d '{
86
- "requestTypeId": "10",
87
- "summary": "New service request",
88
- "description": "Details of the request"
89
- }'
146
+ -d '{"requestTypeId": "10", "summary": "Request title", "description": "Details"}'
90
147
  \`\`\`
91
148
 
92
149
  ## Custom Fields
93
150
 
94
- Custom fields can be passed via the \`customFields\` object when creating or updating issues.
95
- Field mappings (workflow variable to Jira custom field IDs) are configured in the integration settings.
151
+ Pass custom fields via the \`customFields\` object when creating or updating. Field mappings (friendly name to Jira field ID) are configured in integration settings.
96
152
 
97
153
  \`\`\`bash
98
- curl -s -X POST "{{BASE_URL}}/api/jira/issues" \\
154
+ curl -s -X POST http://localhost:5174/api/jira/issues \\
99
155
  -H "Content-Type: application/json" \\
100
- -H "X-Auth-Token: {{AUTH_TOKEN}}" \\
101
- -d '{
102
- "projectKey": "SD",
103
- "issueType": "Change Request",
104
- "summary": "Release v2.1.0",
105
- "description": "Details...",
106
- "customFields": {
107
- "release_name": "v2.1.0",
108
- "environment": "production"
109
- }
110
- }'
156
+ -d '{"summary": "Release v2.1.0", "customFields": {"release_name": "v2.1.0", "environment": "production"}}'
111
157
  \`\`\`
158
+
159
+ ## Notes
160
+ - Auth headers are added automatically by the system.
161
+ - The proxy handles all Jira authentication - never include Jira credentials in requests.
162
+ - All text is automatically converted to Atlassian Document Format (ADF) by the proxy.
163
+ - Use \`curl -s -G ... --data-urlencode\` for JQL queries with special characters.
112
164
  `,
113
165
  };
@@ -31,6 +31,11 @@ router.get('/', (_req, res) => {
31
31
  const triggers = triggerService.getAllTriggers();
32
32
  res.json(triggers);
33
33
  });
34
+ // All matchers evaluated against a specific source message (must be before /:id)
35
+ router.get('/matchers/by-source/:sourceType/:sourceId', (req, res) => {
36
+ const matchers = triggerService.getMatchersBySource(req.params.sourceType, req.params.sourceId);
37
+ res.json({ matchers });
38
+ });
34
39
  // Get single trigger
35
40
  router.get('/:id', (req, res) => {
36
41
  const trigger = triggerService.getTrigger(req.params.id);
@@ -212,6 +217,23 @@ router.get('/:id/events', (req, res) => {
212
217
  const events = triggerService.getTriggerEvents(req.params.id, limit);
213
218
  res.json(events);
214
219
  });
220
+ // ─── Matcher Execution Debugging ───
221
+ // List all matchers that ran for a specific trigger event
222
+ router.get('/events/:eventId/matchers', (req, res) => {
223
+ const eventId = parseInt(req.params.eventId);
224
+ if (isNaN(eventId)) {
225
+ res.status(400).json({ error: 'Invalid eventId' });
226
+ return;
227
+ }
228
+ const matchers = triggerService.getMatchersByEvent(eventId);
229
+ res.json({ matchers });
230
+ });
231
+ // Matcher history for a trigger (across all events)
232
+ router.get('/:triggerId/matcher-history', (req, res) => {
233
+ const limit = parseInt(req.query.limit) || 100;
234
+ const matchers = triggerService.getMatcherHistoryByTrigger(req.params.triggerId, limit);
235
+ res.json({ matchers });
236
+ });
215
237
  // ─── Helper ───
216
238
  function getNestedValue(obj, path) {
217
239
  const parts = path.split('.');