@hotmeshio/long-tail 0.5.10 → 0.5.11
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/build/api/controlplane.d.ts +1 -0
- package/build/api/controlplane.js +1 -0
- package/build/api/diagnostics.d.ts +3 -1
- package/build/api/diagnostics.js +1 -1
- package/build/api/settings.d.ts +3 -2
- package/build/api/settings.js +11 -2
- package/build/modules/version.d.ts +16 -0
- package/build/modules/version.js +34 -0
- package/build/routes/controlplane.js +1 -0
- package/build/routes/diagnostics.js +18 -10
- package/build/services/controlplane/index.js +3 -2
- package/build/services/controlplane/stream-messages-sql.d.ts +4 -3
- package/build/services/controlplane/stream-messages-sql.js +12 -7
- package/build/services/controlplane/types.d.ts +2 -0
- package/build/services/diagnostics/index.d.ts +15 -3
- package/build/services/diagnostics/index.js +71 -32
- package/build/services/diagnostics/patterns.d.ts +13 -3
- package/build/services/diagnostics/patterns.js +25 -14
- package/build/system/mcp-servers/admin/diagnostics.d.ts +9 -3
- package/build/system/mcp-servers/admin/diagnostics.js +31 -21
- package/build/system/mcp-servers/admin/schemas.d.ts +9 -3
- package/build/system/mcp-servers/admin/schemas.js +5 -3
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/auth.d.ts +1 -1
- package/dashboard/dist/assets/{AdminDashboard-0LSslIQ7.js → AdminDashboard-DZpduYdW.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-0LSslIQ7.js.map → AdminDashboard-DZpduYdW.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-hqBr1540.js → AgentConfigPage-SFnHMIP6.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-hqBr1540.js.map → AgentConfigPage-SFnHMIP6.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-bnXR1ezD.js → AgentDetailPage-C-2iSaDs.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-bnXR1ezD.js.map → AgentDetailPage-C-2iSaDs.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-tJIlrnfM.js → AgentsPage-C3FztFd_.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-tJIlrnfM.js.map → AgentsPage-C3FztFd_.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-B7gEfcw3.js → AvailableEscalationsPage-DIqdBCwe.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-B7gEfcw3.js.map → AvailableEscalationsPage-DIqdBCwe.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-fFE4GAaO.js → BotPicker-Dse2jW4b.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-fFE4GAaO.js.map → BotPicker-Dse2jW4b.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-DotZ7iQc.js → CapabilitiesPage-DTfKCzBQ.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-DotZ7iQc.js.map → CapabilitiesPage-DTfKCzBQ.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-D97SYypF.js → CollapsibleSection-8vf38J6K.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-D97SYypF.js.map → CollapsibleSection-8vf38J6K.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-C08U6dlv.js → CredentialsPage-7C4M-hrH.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-C08U6dlv.js.map → CredentialsPage-7C4M-hrH.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-C2TuCl4h.js → CronLabel-DrGWtaSY.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-C2TuCl4h.js.map → CronLabel-DrGWtaSY.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-C3zGsESx.js → CustomDurationPicker-Vcmr4LiR.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-C3zGsESx.js.map → CustomDurationPicker-Vcmr4LiR.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-DF4m0cv_.js → ElapsedCell-BNexhR5d.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-DF4m0cv_.js.map → ElapsedCell-BNexhR5d.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-blGOYKtJ.js → EscalationsOverview-BI3DEwYP.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-blGOYKtJ.js.map → EscalationsOverview-BI3DEwYP.js.map} +1 -1
- package/dashboard/dist/assets/EventTable-CwvX_L9t.js +2 -0
- package/dashboard/dist/assets/EventTable-CwvX_L9t.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-QVAUHmjI.js → GraphInvokePage-C-3b8WK0.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-QVAUHmjI.js.map → GraphInvokePage-C-3b8WK0.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-DvYgvCcO.js → HomePage-BS_-jcHr.js} +2 -2
- package/dashboard/dist/assets/{HomePage-DvYgvCcO.js.map → HomePage-BS_-jcHr.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-D6tQ3ZG2.js → ListToolbar-BH9rwQVE.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-D6tQ3ZG2.js.map → ListToolbar-BH9rwQVE.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-BClbGYb8.js → McpOverview-C9iOdE7E.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-BClbGYb8.js.map → McpOverview-C9iOdE7E.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-DHjfDRdm.js → McpQueryDetailPage-DMN-yqvJ.js} +3 -3
- package/dashboard/dist/assets/{McpQueryDetailPage-DHjfDRdm.js.map → McpQueryDetailPage-DMN-yqvJ.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-qtEuMDrd.js → McpQueryPage-Db1LUKVs.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-qtEuMDrd.js.map → McpQueryPage-Db1LUKVs.js.map} +1 -1
- package/dashboard/dist/assets/McpRunDetailPage-Wb47sRvo.js +2 -0
- package/dashboard/dist/assets/{McpRunDetailPage-DRbIZMU2.js.map → McpRunDetailPage-Wb47sRvo.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CMfwXQ4R.js → McpRunsPage--tgT5uo7.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CMfwXQ4R.js.map → McpRunsPage--tgT5uo7.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-Dq2pfV68.js → OperatorDashboard-BWVUbJkZ.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-Dq2pfV68.js.map → OperatorDashboard-BWVUbJkZ.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-DjQuNERz.js → ProcessDetailPage-CvPx-h8y.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-DjQuNERz.js.map → ProcessDetailPage-CvPx-h8y.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-vJimii8-.js → ProcessesListPage-CWiRM_h3.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-vJimii8-.js.map → ProcessesListPage-CWiRM_h3.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-CFCN_mRF.js → RolesPage-W5ZjOHpg.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-CFCN_mRF.js.map → RolesPage-W5ZjOHpg.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-sBxAvlRf.js → RunAsSelector-CvxGTNGT.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-sBxAvlRf.js.map → RunAsSelector-CvxGTNGT.js.map} +1 -1
- package/dashboard/dist/assets/StreamMessageDetail-BF0tM0cd.js +2 -0
- package/dashboard/dist/assets/StreamMessageDetail-BF0tM0cd.js.map +1 -0
- package/dashboard/dist/assets/SwimlaneTimeline-Bv-KHY9s.js +2 -0
- package/dashboard/dist/assets/SwimlaneTimeline-Bv-KHY9s.js.map +1 -0
- package/dashboard/dist/assets/{TaskDetailPage-CgFN5uWn.js → TaskDetailPage-Bzb_fuLy.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CgFN5uWn.js.map → TaskDetailPage-Bzb_fuLy.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-qj25rRZL.js → TasksListPage-CBezWaZL.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-qj25rRZL.js.map → TasksListPage-CBezWaZL.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-C7LylVjP.js → TimeAgo-CKHfTzat.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-C7LylVjP.js.map → TimeAgo-CKHfTzat.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-CrCtoBlo.js → TimestampCell-Dsje8GH-.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-CrCtoBlo.js.map → TimestampCell-Dsje8GH-.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-DYGlB-QW.js → ToolTestPanel-3OcG18e5.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-DYGlB-QW.js.map → ToolTestPanel-3OcG18e5.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-DLY6X_R6.js → TopicDetailPage-DCtv91hH.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-DLY6X_R6.js.map → TopicDetailPage-DCtv91hH.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-C5nOLsLx.js → TopicsPage-DVGSNc_E.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-C5nOLsLx.js.map → TopicsPage-DVGSNc_E.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-CgUZyiwm.js → UserName-xHAgkk8M.js} +2 -2
- package/dashboard/dist/assets/{UserName-CgUZyiwm.js.map → UserName-xHAgkk8M.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-CWNxsS8a.js +2 -0
- package/dashboard/dist/assets/{WorkflowExecutionPage-CDr5CTM2.js.map → WorkflowExecutionPage-CWNxsS8a.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-tCBP0i0Y.js → WorkflowsDashboard-M13lIaw7.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-tCBP0i0Y.js.map → WorkflowsDashboard-M13lIaw7.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-CaMeZwde.js → WorkflowsOverview-e7jWSj_n.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CaMeZwde.js.map → WorkflowsOverview-e7jWSj_n.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C3uyCiIX.js → YamlWorkflowDetailPage-CdAeam76.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C3uyCiIX.js.map → YamlWorkflowDetailPage-CdAeam76.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-DRnYgsvA.js → YamlWorkflowsPage-S_gKs2Di.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-DRnYgsvA.js.map → YamlWorkflowsPage-S_gKs2Di.js.map} +1 -1
- package/dashboard/dist/assets/{agents-HMkTc425.js → agents-JE4aL07D.js} +2 -2
- package/dashboard/dist/assets/{agents-HMkTc425.js.map → agents-JE4aL07D.js.map} +1 -1
- package/dashboard/dist/assets/{bots-CDe2jXNx.js → bots-cAzKkDtQ.js} +2 -2
- package/dashboard/dist/assets/{bots-CDe2jXNx.js.map → bots-cAzKkDtQ.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-DhnM7Ztb.js → capabilities-RBFXLAXF.js} +2 -2
- package/dashboard/dist/assets/{capabilities-DhnM7Ztb.js.map → capabilities-RBFXLAXF.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-BwIJ9ZC_.js → controlplane-DGhv3rwI.js} +2 -2
- package/dashboard/dist/assets/{controlplane-BwIJ9ZC_.js.map → controlplane-DGhv3rwI.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-BKqSywVA.js → escalation-columns-DqTkKbCv.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-BKqSywVA.js.map → escalation-columns-DqTkKbCv.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-Djk5XKmX.js → escalation-ne7dIaPa.js} +2 -2
- package/dashboard/dist/assets/{escalation-Djk5XKmX.js.map → escalation-ne7dIaPa.js.map} +1 -1
- package/dashboard/dist/assets/{index-DRIJM1L-.js → index-BTqhAT6p.js} +2 -2
- package/dashboard/dist/assets/{index-DRIJM1L-.js.map → index-BTqhAT6p.js.map} +1 -1
- package/dashboard/dist/assets/{index-BW-fIquS.js → index-BbJuC9kg.js} +2 -2
- package/dashboard/dist/assets/{index-BW-fIquS.js.map → index-BbJuC9kg.js.map} +1 -1
- package/dashboard/dist/assets/{index-mEQ6kCnt.js → index-C2l4GcZ7.js} +2 -2
- package/dashboard/dist/assets/{index-mEQ6kCnt.js.map → index-C2l4GcZ7.js.map} +1 -1
- package/dashboard/dist/assets/{index-BYpWOUEe.js → index-CByjpYlU.js} +2 -2
- package/dashboard/dist/assets/{index-BYpWOUEe.js.map → index-CByjpYlU.js.map} +1 -1
- package/dashboard/dist/assets/{index-rUST2YsO.js → index-CEpw_Fh8.js} +2 -2
- package/dashboard/dist/assets/{index-rUST2YsO.js.map → index-CEpw_Fh8.js.map} +1 -1
- package/dashboard/dist/assets/{index-CPIYW81T.js → index-CQDn-bLr.js} +2 -2
- package/dashboard/dist/assets/{index-CPIYW81T.js.map → index-CQDn-bLr.js.map} +1 -1
- package/dashboard/dist/assets/{index-DBi9Q9Hw.js → index-CgyucK0e.js} +2 -2
- package/dashboard/dist/assets/{index-DBi9Q9Hw.js.map → index-CgyucK0e.js.map} +1 -1
- package/dashboard/dist/assets/index-ClvYSNZd.css +1 -0
- package/dashboard/dist/assets/{index-E2hQ8DIi.js → index-CucXk58o.js} +2 -2
- package/dashboard/dist/assets/{index-E2hQ8DIi.js.map → index-CucXk58o.js.map} +1 -1
- package/dashboard/dist/assets/{index-BFQ2t-p1.js → index-DPJW96d2.js} +21 -21
- package/dashboard/dist/assets/index-DPJW96d2.js.map +1 -0
- package/dashboard/dist/assets/index-DeZhNCf5.js +2 -0
- package/dashboard/dist/assets/index-DeZhNCf5.js.map +1 -0
- package/dashboard/dist/assets/{index-BNkpns_E.js → index-DgzgRTNg.js} +2 -2
- package/dashboard/dist/assets/{index-BNkpns_E.js.map → index-DgzgRTNg.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cydgj_Co.js → index-Do8Xp3pa.js} +2 -2
- package/dashboard/dist/assets/{index-Cydgj_Co.js.map → index-Do8Xp3pa.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dhy2PVAw.js → index-QxAYCJ1S.js} +2 -2
- package/dashboard/dist/assets/{index-Dhy2PVAw.js.map → index-QxAYCJ1S.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-jQE3dBVi.js → knowledge-DzRpsm3D.js} +2 -2
- package/dashboard/dist/assets/{knowledge-jQE3dBVi.js.map → knowledge-DzRpsm3D.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-DtEGfZiz.js → mcp-B1lc6nL4.js} +2 -2
- package/dashboard/dist/assets/{mcp-DtEGfZiz.js.map → mcp-B1lc6nL4.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CiTLVuDC.js → mcp-query-CXuweaN3.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CiTLVuDC.js.map → mcp-query-CXuweaN3.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-ClNdzuUK.js → pipelines-D4VLWDvu.js} +2 -2
- package/dashboard/dist/assets/{pipelines-ClNdzuUK.js.map → pipelines-D4VLWDvu.js.map} +1 -1
- package/dashboard/dist/assets/{roles-BmHbRaPT.js → roles-CSpHRRu8.js} +2 -2
- package/dashboard/dist/assets/{roles-BmHbRaPT.js.map → roles-CSpHRRu8.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-BPONxLcq.js → tasks-CWUsKhM4.js} +2 -2
- package/dashboard/dist/assets/{tasks-BPONxLcq.js.map → tasks-CWUsKhM4.js.map} +1 -1
- package/dashboard/dist/assets/{topics-XREPRxwo.js → topics-BXwstrzB.js} +2 -2
- package/dashboard/dist/assets/{topics-XREPRxwo.js.map → topics-BXwstrzB.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-M7SaEfel.js → useEventHooks-CjygrbYy.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-M7SaEfel.js.map → useEventHooks-CjygrbYy.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BQHGkdBv.js → useNamespace-CsepDyR5.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BQHGkdBv.js.map → useNamespace-CsepDyR5.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-B7JtUGSE.js → useYamlActivityEvents-gWIG6akB.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-B7JtUGSE.js.map → useYamlActivityEvents-gWIG6akB.js.map} +1 -1
- package/dashboard/dist/assets/{users-BpjSNhQe.js → users-DvhHbR0R.js} +2 -2
- package/dashboard/dist/assets/{users-BpjSNhQe.js.map → users-DvhHbR0R.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-k4jE0ECC.js → workflows-C7Cam2Ok.js} +2 -2
- package/dashboard/dist/assets/{workflows-k4jE0ECC.js.map → workflows-C7Cam2Ok.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-DpviSLEL.js → yaml-workflows-ChUOE22W.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-DpviSLEL.js.map → yaml-workflows-ChUOE22W.js.map} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/docs/api/mcp/admin.md +94 -1
- package/docs/mcp.md +102 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/EventTable-DzrMH-ux.js +0 -2
- package/dashboard/dist/assets/EventTable-DzrMH-ux.js.map +0 -1
- package/dashboard/dist/assets/McpRunDetailPage-DRbIZMU2.js +0 -2
- package/dashboard/dist/assets/SwimlaneTimeline-2Eet4OOL.js +0 -2
- package/dashboard/dist/assets/SwimlaneTimeline-2Eet4OOL.js.map +0 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-CDr5CTM2.js +0 -2
- package/dashboard/dist/assets/index-BFQ2t-p1.js.map +0 -1
- package/dashboard/dist/assets/index-DSki_Ub6.css +0 -1
- package/dashboard/dist/assets/index-u08vQUaw.js +0 -2
- package/dashboard/dist/assets/index-u08vQUaw.js.map +0 -1
|
@@ -2,14 +2,16 @@ import type { LTApiResult } from '../types/sdk';
|
|
|
2
2
|
export declare function diagnose(input: {
|
|
3
3
|
workflowId: string;
|
|
4
4
|
appId?: string;
|
|
5
|
+
maxEvents?: number;
|
|
5
6
|
}): Promise<LTApiResult>;
|
|
6
7
|
export declare function stalledJobs(input: {
|
|
7
8
|
appId?: string;
|
|
8
|
-
|
|
9
|
+
idleMinutes?: number;
|
|
9
10
|
workflowType?: string;
|
|
10
11
|
limit?: number;
|
|
11
12
|
}): Promise<LTApiResult>;
|
|
12
13
|
export declare function orphanedSignals(input: {
|
|
13
14
|
appId?: string;
|
|
15
|
+
withinHours?: number;
|
|
14
16
|
limit?: number;
|
|
15
17
|
}): Promise<LTApiResult>;
|
package/build/api/diagnostics.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.orphanedSignals = orphanedSignals;
|
|
|
6
6
|
const diagnostics_1 = require("../services/diagnostics");
|
|
7
7
|
async function diagnose(input) {
|
|
8
8
|
try {
|
|
9
|
-
const diagnosis = await (0, diagnostics_1.diagnoseJob)(input.workflowId, input.appId);
|
|
9
|
+
const diagnosis = await (0, diagnostics_1.diagnoseJob)(input.workflowId, input.appId, { maxEvents: input.maxEvents });
|
|
10
10
|
return { status: 200, data: diagnosis };
|
|
11
11
|
}
|
|
12
12
|
catch (err) {
|
package/build/api/settings.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import type { LTApiResult } from '../types/sdk';
|
|
|
4
4
|
* Return platform settings for the current deployment.
|
|
5
5
|
*
|
|
6
6
|
* Includes telemetry configuration (trace URL), escalation claim duration
|
|
7
|
-
* options,
|
|
7
|
+
* options, event transport details (socket.io, NATS, or none), and an
|
|
8
|
+
* environment block (long-tail + HotMesh versions, Node env/version).
|
|
8
9
|
*
|
|
9
10
|
* @param req — the incoming HTTP request, used to derive NATS WS URL from headers when proxying
|
|
10
|
-
* @returns `{ status: 200, data: { telemetry, escalation, events } }`
|
|
11
|
+
* @returns `{ status: 200, data: { telemetry, escalation, events, auth, ai, environment } }`
|
|
11
12
|
*/
|
|
12
13
|
export declare function getSettings(req?: IncomingMessage): Promise<LTApiResult>;
|
package/build/api/settings.js
CHANGED
|
@@ -7,6 +7,7 @@ const nats_1 = require("../lib/events/nats");
|
|
|
7
7
|
const socketio_1 = require("../lib/events/socketio");
|
|
8
8
|
const nats_ws_proxy_1 = require("../lib/events/nats-ws-proxy");
|
|
9
9
|
const config_1 = require("../modules/config");
|
|
10
|
+
const version_1 = require("../modules/version");
|
|
10
11
|
const sso_1 = require("../modules/sso");
|
|
11
12
|
const defaults_1 = require("../modules/defaults");
|
|
12
13
|
const llm_1 = require("../services/llm");
|
|
@@ -33,10 +34,11 @@ function resolveNatsWsUrl(adapter, req) {
|
|
|
33
34
|
* Return platform settings for the current deployment.
|
|
34
35
|
*
|
|
35
36
|
* Includes telemetry configuration (trace URL), escalation claim duration
|
|
36
|
-
* options,
|
|
37
|
+
* options, event transport details (socket.io, NATS, or none), and an
|
|
38
|
+
* environment block (long-tail + HotMesh versions, Node env/version).
|
|
37
39
|
*
|
|
38
40
|
* @param req — the incoming HTTP request, used to derive NATS WS URL from headers when proxying
|
|
39
|
-
* @returns `{ status: 200, data: { telemetry, escalation, events } }`
|
|
41
|
+
* @returns `{ status: 200, data: { telemetry, escalation, events, auth, ai, environment } }`
|
|
40
42
|
*/
|
|
41
43
|
async function getSettings(req) {
|
|
42
44
|
try {
|
|
@@ -68,6 +70,13 @@ async function getSettings(req) {
|
|
|
68
70
|
ai: {
|
|
69
71
|
enabled: (0, llm_1.hasLLMApiKey)(),
|
|
70
72
|
},
|
|
73
|
+
environment: {
|
|
74
|
+
longTailVersion: version_1.LONG_TAIL_VERSION,
|
|
75
|
+
hotmeshVersion: version_1.HOTMESH_VERSION,
|
|
76
|
+
nodeEnv: config_1.config.NODE_ENV,
|
|
77
|
+
nodeVersion: process.version,
|
|
78
|
+
eventTransport: transport,
|
|
79
|
+
},
|
|
71
80
|
},
|
|
72
81
|
};
|
|
73
82
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime version constants for the environment block surfaced by /api/settings.
|
|
3
|
+
*
|
|
4
|
+
* The HotMesh SDK version is read from its package.json — node module
|
|
5
|
+
* resolution finds it in both dev (source) and prod (the runtime image's
|
|
6
|
+
* node_modules), and resolveJsonModule handles the static import at compile time.
|
|
7
|
+
*/
|
|
8
|
+
export declare const HOTMESH_VERSION: string;
|
|
9
|
+
/**
|
|
10
|
+
* The long-tail app version. Read from the root package.json at runtime via the
|
|
11
|
+
* process working directory (which is the app root in both dev and the Docker
|
|
12
|
+
* image, where CMD runs `node build/index.js` from /app). A static
|
|
13
|
+
* `../package.json` import would resolve to build/package.json after compile —
|
|
14
|
+
* a path the Dockerfile does not copy — so this reads from cwd instead.
|
|
15
|
+
*/
|
|
16
|
+
export declare const LONG_TAIL_VERSION: string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LONG_TAIL_VERSION = exports.HOTMESH_VERSION = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const package_json_1 = __importDefault(require("@hotmeshio/hotmesh/package.json"));
|
|
10
|
+
/**
|
|
11
|
+
* Runtime version constants for the environment block surfaced by /api/settings.
|
|
12
|
+
*
|
|
13
|
+
* The HotMesh SDK version is read from its package.json — node module
|
|
14
|
+
* resolution finds it in both dev (source) and prod (the runtime image's
|
|
15
|
+
* node_modules), and resolveJsonModule handles the static import at compile time.
|
|
16
|
+
*/
|
|
17
|
+
exports.HOTMESH_VERSION = package_json_1.default.version ?? 'unknown';
|
|
18
|
+
/**
|
|
19
|
+
* The long-tail app version. Read from the root package.json at runtime via the
|
|
20
|
+
* process working directory (which is the app root in both dev and the Docker
|
|
21
|
+
* image, where CMD runs `node build/index.js` from /app). A static
|
|
22
|
+
* `../package.json` import would resolve to build/package.json after compile —
|
|
23
|
+
* a path the Dockerfile does not copy — so this reads from cwd instead.
|
|
24
|
+
*/
|
|
25
|
+
exports.LONG_TAIL_VERSION = readLongTailVersion();
|
|
26
|
+
function readLongTailVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const raw = (0, fs_1.readFileSync)((0, path_1.resolve)(process.cwd(), 'package.json'), 'utf8');
|
|
29
|
+
return JSON.parse(raw).version ?? 'unknown';
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return 'unknown';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -123,6 +123,7 @@ router.get('/stream-messages', auth_1.requireBuilder, async (req, res) => {
|
|
|
123
123
|
workflow_name: req.query.workflow_name || undefined,
|
|
124
124
|
jid: req.query.jid || undefined,
|
|
125
125
|
aid: req.query.aid || undefined,
|
|
126
|
+
dad: req.query.dad || undefined,
|
|
126
127
|
});
|
|
127
128
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
128
129
|
});
|
|
@@ -42,29 +42,33 @@ const router = (0, express_1.Router)();
|
|
|
42
42
|
* Full diagnosis for a single workflow job.
|
|
43
43
|
*
|
|
44
44
|
* Query params:
|
|
45
|
-
* app_id
|
|
45
|
+
* app_id — HotMesh namespace (default: durable)
|
|
46
|
+
* max_events — cap on events returned (most recent kept; default: 500)
|
|
46
47
|
*/
|
|
47
48
|
router.get('/jobs/:workflowId', auth_1.requireAdmin, async (req, res) => {
|
|
48
49
|
const result = await api.diagnose({
|
|
49
50
|
workflowId: req.params.workflowId,
|
|
50
51
|
appId: typeof req.query.app_id === 'string' ? req.query.app_id : undefined,
|
|
52
|
+
maxEvents: req.query.max_events ? Number(req.query.max_events) : undefined,
|
|
51
53
|
});
|
|
52
54
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
53
55
|
});
|
|
54
56
|
/**
|
|
55
57
|
* GET /api/diagnostics/stalled
|
|
56
|
-
* Running jobs with no recent
|
|
58
|
+
* Running jobs with no recent status change. A frozen updated_at is the NORMAL
|
|
59
|
+
* signature of a workflow waiting at a condition()/waitFor()/sleepFor() — each
|
|
60
|
+
* row is classified `likely: waiting | no_recent_progress` accordingly.
|
|
57
61
|
*
|
|
58
62
|
* Query params:
|
|
59
|
-
* app_id
|
|
60
|
-
*
|
|
61
|
-
* workflow_type
|
|
62
|
-
* limit
|
|
63
|
+
* app_id — HotMesh namespace (default: durable)
|
|
64
|
+
* idle_minutes — minimum minutes since last status change (default: 5)
|
|
65
|
+
* workflow_type — filter by workflow function name
|
|
66
|
+
* limit — max results (default: 50, max: 200)
|
|
63
67
|
*/
|
|
64
68
|
router.get('/stalled', auth_1.requireAdmin, async (req, res) => {
|
|
65
69
|
const result = await api.stalledJobs({
|
|
66
70
|
appId: typeof req.query.app_id === 'string' ? req.query.app_id : undefined,
|
|
67
|
-
|
|
71
|
+
idleMinutes: req.query.idle_minutes ? Number(req.query.idle_minutes) : undefined,
|
|
68
72
|
workflowType: typeof req.query.workflow_type === 'string' ? req.query.workflow_type : undefined,
|
|
69
73
|
limit: req.query.limit ? Number(req.query.limit) : undefined,
|
|
70
74
|
});
|
|
@@ -72,15 +76,19 @@ router.get('/stalled', auth_1.requireAdmin, async (req, res) => {
|
|
|
72
76
|
});
|
|
73
77
|
/**
|
|
74
78
|
* GET /api/diagnostics/orphaned-signals
|
|
75
|
-
* Suspended
|
|
79
|
+
* Suspended waiters with no escalation row, within a recent time window. The
|
|
80
|
+
* window keeps this off a full-history scan of the partitioned worker_streams
|
|
81
|
+
* table — widen `within_hours` deliberately to reach older orphans.
|
|
76
82
|
*
|
|
77
83
|
* Query params:
|
|
78
|
-
* app_id
|
|
79
|
-
*
|
|
84
|
+
* app_id — HotMesh namespace (default: durable)
|
|
85
|
+
* within_hours — recent window to scan (default: 24, max: 720)
|
|
86
|
+
* limit — max results (default: 100, max: 500)
|
|
80
87
|
*/
|
|
81
88
|
router.get('/orphaned-signals', auth_1.requireAdmin, async (req, res) => {
|
|
82
89
|
const result = await api.orphanedSignals({
|
|
83
90
|
appId: typeof req.query.app_id === 'string' ? req.query.app_id : undefined,
|
|
91
|
+
withinHours: req.query.within_hours ? Number(req.query.within_hours) : undefined,
|
|
84
92
|
limit: req.query.limit ? Number(req.query.limit) : undefined,
|
|
85
93
|
});
|
|
86
94
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
@@ -129,9 +129,10 @@ async function getStreamMessages(schema, params) {
|
|
|
129
129
|
const workflowName = params.workflow_name ?? null;
|
|
130
130
|
const jid = params.jid ?? null;
|
|
131
131
|
const aid = params.aid ?? null;
|
|
132
|
+
const dad = params.dad ?? null;
|
|
132
133
|
const pool = (0, db_1.getPool)();
|
|
133
|
-
const queryParams = [streamName, status, msgType, topic, workflowName, jid, aid, limit, offset];
|
|
134
|
-
const countParams = [streamName, status, msgType, topic, workflowName, jid, aid];
|
|
134
|
+
const queryParams = [streamName, status, msgType, topic, workflowName, jid, aid, dad, limit, offset];
|
|
135
|
+
const countParams = [streamName, status, msgType, topic, workflowName, jid, aid, dad];
|
|
135
136
|
const [messagesRes, countRes] = await Promise.all([
|
|
136
137
|
pool.query((0, stream_messages_sql_1.LIST_STREAM_MESSAGES)(schema, sortColumn, sortOrder, source), queryParams).catch(() => ({ rows: [] })),
|
|
137
138
|
pool.query((0, stream_messages_sql_1.COUNT_STREAM_MESSAGES)(schema, source), countParams).catch(() => ({ rows: [{ count: 0 }] })),
|
|
@@ -9,13 +9,14 @@ export declare const VALID_SORT_ORDERS: Set<string>;
|
|
|
9
9
|
* with different schemas and must never be commingled in a single query.
|
|
10
10
|
*
|
|
11
11
|
* Parameters: $1 = stream_name, $2 = status, $3 = msg_type,
|
|
12
|
-
* $4 = topic, $5 = workflow_name, $6 = jid, $7 = aid,
|
|
13
|
-
* $
|
|
12
|
+
* $4 = topic, $5 = workflow_name, $6 = jid, $7 = aid, $8 = dad,
|
|
13
|
+
* $9 = limit, $10 = offset
|
|
14
14
|
*/
|
|
15
15
|
export declare function LIST_STREAM_MESSAGES(schema: string, sortColumn: string, sortOrder: string, source: 'engine' | 'worker'): string;
|
|
16
16
|
/**
|
|
17
17
|
* Count stream messages matching the filter criteria.
|
|
18
18
|
*
|
|
19
|
-
* Parameters: $1 = stream_name, $2 = status, $3 = msg_type
|
|
19
|
+
* Parameters: $1 = stream_name, $2 = status, $3 = msg_type,
|
|
20
|
+
* $4 = topic, $5 = workflow_name, $6 = jid, $7 = aid, $8 = dad
|
|
20
21
|
*/
|
|
21
22
|
export declare function COUNT_STREAM_MESSAGES(schema: string, source: 'engine' | 'worker'): string;
|
|
@@ -42,8 +42,10 @@ const STATUS_CASE = `
|
|
|
42
42
|
* $5 = workflow_name filter (NULL = all, worker-only)
|
|
43
43
|
* $6 = jid filter (NULL = all, worker-only)
|
|
44
44
|
* $7 = aid filter (NULL = all, worker-only)
|
|
45
|
+
* $8 = dad filter (NULL = all, worker-only — pins one execution dimension)
|
|
45
46
|
*/
|
|
46
|
-
/** Engine WHERE: supports stream_name, status, and jid (v0.18.0).
|
|
47
|
+
/** Engine WHERE: supports stream_name, status, and jid (v0.18.0). engine_streams
|
|
48
|
+
* has no aid/dad columns, so $7/$8 are accepted but inert here. */
|
|
47
49
|
const ENGINE_WHERE_CLAUSE = `
|
|
48
50
|
WHERE ($1::text IS NULL OR stream_name ILIKE $1)
|
|
49
51
|
AND ($2::text IS NULL OR ${STATUS_CASE} = $2)
|
|
@@ -51,7 +53,8 @@ const ENGINE_WHERE_CLAUSE = `
|
|
|
51
53
|
AND ($4::text IS NULL)
|
|
52
54
|
AND ($5::text IS NULL)
|
|
53
55
|
AND ($6::text IS NULL OR jid = $6)
|
|
54
|
-
AND ($7::text IS NULL)
|
|
56
|
+
AND ($7::text IS NULL)
|
|
57
|
+
AND ($8::text IS NULL)`;
|
|
55
58
|
const WORKER_WHERE_CLAUSE = `
|
|
56
59
|
WHERE ($1::text IS NULL OR stream_name ILIKE $1)
|
|
57
60
|
AND ($2::text IS NULL OR ${STATUS_CASE} = $2)
|
|
@@ -59,7 +62,8 @@ const WORKER_WHERE_CLAUSE = `
|
|
|
59
62
|
AND ($4::text IS NULL OR topic = $4)
|
|
60
63
|
AND ($5::text IS NULL OR workflow_name = $5)
|
|
61
64
|
AND ($6::text IS NULL OR jid = $6)
|
|
62
|
-
AND ($7::text IS NULL OR aid = $7)
|
|
65
|
+
AND ($7::text IS NULL OR aid = $7)
|
|
66
|
+
AND ($8::text IS NULL OR dad = $8)`;
|
|
63
67
|
/**
|
|
64
68
|
* List stream messages with pagination, filtering, and sorting.
|
|
65
69
|
*
|
|
@@ -67,8 +71,8 @@ const WORKER_WHERE_CLAUSE = `
|
|
|
67
71
|
* with different schemas and must never be commingled in a single query.
|
|
68
72
|
*
|
|
69
73
|
* Parameters: $1 = stream_name, $2 = status, $3 = msg_type,
|
|
70
|
-
* $4 = topic, $5 = workflow_name, $6 = jid, $7 = aid,
|
|
71
|
-
* $
|
|
74
|
+
* $4 = topic, $5 = workflow_name, $6 = jid, $7 = aid, $8 = dad,
|
|
75
|
+
* $9 = limit, $10 = offset
|
|
72
76
|
*/
|
|
73
77
|
function LIST_STREAM_MESSAGES(schema, sortColumn, sortOrder, source) {
|
|
74
78
|
const select = source === 'engine'
|
|
@@ -94,12 +98,13 @@ function LIST_STREAM_MESSAGES(schema, sortColumn, sortOrder, source) {
|
|
|
94
98
|
return `
|
|
95
99
|
SELECT * FROM (${select}) AS q
|
|
96
100
|
ORDER BY ${sortColumn} ${sortOrder}, id DESC
|
|
97
|
-
LIMIT $
|
|
101
|
+
LIMIT $9::int OFFSET $10::int`;
|
|
98
102
|
}
|
|
99
103
|
/**
|
|
100
104
|
* Count stream messages matching the filter criteria.
|
|
101
105
|
*
|
|
102
|
-
* Parameters: $1 = stream_name, $2 = status, $3 = msg_type
|
|
106
|
+
* Parameters: $1 = stream_name, $2 = status, $3 = msg_type,
|
|
107
|
+
* $4 = topic, $5 = workflow_name, $6 = jid, $7 = aid, $8 = dad
|
|
103
108
|
*/
|
|
104
109
|
function COUNT_STREAM_MESSAGES(schema, source) {
|
|
105
110
|
const table = source === 'engine' ? 'engine_streams' : 'worker_streams';
|
|
@@ -51,6 +51,8 @@ export interface StreamMessagesParams {
|
|
|
51
51
|
workflow_name?: string | null;
|
|
52
52
|
jid?: string | null;
|
|
53
53
|
aid?: string | null;
|
|
54
|
+
/** Dimension/ancestor path (worker-only) — pins one execution among siblings sharing an aid. */
|
|
55
|
+
dad?: string | null;
|
|
54
56
|
}
|
|
55
57
|
export interface StreamMessagesResult {
|
|
56
58
|
messages: StreamMessage[];
|
|
@@ -21,22 +21,32 @@ export interface JobDiagnosis {
|
|
|
21
21
|
workflow_id: string;
|
|
22
22
|
app_id: string;
|
|
23
23
|
status: 'running' | 'completed' | 'failed' | 'not_found';
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Milliseconds since the last execution event. NOT a fault signal on its own —
|
|
26
|
+
* a workflow can sit at a condition()/waitFor()/sleepFor() for days legitimately.
|
|
27
|
+
* Read `findings` for the actual interpretation.
|
|
28
|
+
*/
|
|
29
|
+
idle_for_ms: number | null;
|
|
25
30
|
workflow_type: string | null;
|
|
26
31
|
last_event_at: string | null;
|
|
27
32
|
stream_summary: StreamSummary;
|
|
28
33
|
escalation: EscalationSummary;
|
|
29
34
|
findings: Finding[];
|
|
30
35
|
execution_events: unknown[];
|
|
36
|
+
/** Total events before any cap was applied (execution_events may be truncated to the most recent `maxEvents`). */
|
|
37
|
+
total_events: number;
|
|
38
|
+
events_truncated: boolean;
|
|
31
39
|
stream_messages: {
|
|
32
40
|
worker: StreamMessage[];
|
|
33
41
|
engine: StreamMessage[];
|
|
34
42
|
};
|
|
35
43
|
}
|
|
36
|
-
export declare function diagnoseJob(workflowId: string, appId?: string
|
|
44
|
+
export declare function diagnoseJob(workflowId: string, appId?: string, options?: {
|
|
45
|
+
maxEvents?: number;
|
|
46
|
+
}): Promise<JobDiagnosis>;
|
|
37
47
|
export declare function findStalledJobs(params: {
|
|
38
48
|
appId?: string;
|
|
39
|
-
|
|
49
|
+
idleMinutes?: number;
|
|
40
50
|
workflowType?: string | null;
|
|
41
51
|
limit?: number;
|
|
42
52
|
}): Promise<{
|
|
@@ -45,8 +55,10 @@ export declare function findStalledJobs(params: {
|
|
|
45
55
|
}>;
|
|
46
56
|
export declare function findOrphanedSignals(params: {
|
|
47
57
|
appId?: string;
|
|
58
|
+
withinHours?: number;
|
|
48
59
|
limit?: number;
|
|
49
60
|
}): Promise<{
|
|
50
61
|
orphans: unknown[];
|
|
51
62
|
total: number;
|
|
63
|
+
within_hours: number;
|
|
52
64
|
}>;
|
|
@@ -15,34 +15,56 @@ const ESCALATION_BY_JOB = `
|
|
|
15
15
|
WHERE workflow_id = $1
|
|
16
16
|
ORDER BY created_at DESC LIMIT 5
|
|
17
17
|
`;
|
|
18
|
+
// "No recent status change" is NOT the same as "broken": HotMesh only bumps
|
|
19
|
+
// jobs.updated_at when status changes, so a frozen updated_at is the normal
|
|
20
|
+
// signature of a workflow waiting at a condition()/waitFor()/sleepFor(). We
|
|
21
|
+
// classify each row with `has_open_escalation` (a cheap, workflow_id-indexed
|
|
22
|
+
// EXISTS bounded by the outer LIMIT) so callers can tell a healthy wait
|
|
23
|
+
// (`likely = waiting`) from a candidate worth a closer look (`no_recent_progress`).
|
|
18
24
|
const STALLED_JOBS = (appId) => `
|
|
19
25
|
SELECT
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
q.*,
|
|
27
|
+
CASE WHEN q.has_open_escalation THEN 'waiting' ELSE 'no_recent_progress' END AS likely
|
|
28
|
+
FROM (
|
|
29
|
+
SELECT
|
|
30
|
+
REPLACE(j.key, $1, '') AS workflow_id,
|
|
31
|
+
j.entity AS workflow_type,
|
|
32
|
+
j.status,
|
|
33
|
+
j.created_at,
|
|
34
|
+
j.updated_at,
|
|
35
|
+
EXTRACT(EPOCH FROM (NOW() - j.updated_at)) * 1000 AS idle_ms,
|
|
36
|
+
EXISTS (
|
|
37
|
+
SELECT 1 FROM public.hmsh_escalations e
|
|
38
|
+
WHERE e.workflow_id = REPLACE(j.key, $1, '') AND e.status = 'pending'
|
|
39
|
+
) AS has_open_escalation
|
|
40
|
+
FROM "${appId}".jobs j
|
|
41
|
+
WHERE j.status > 0
|
|
42
|
+
AND j.is_live = TRUE
|
|
43
|
+
AND j.updated_at < NOW() - ($2 || ' minutes')::INTERVAL
|
|
44
|
+
AND ($3::text IS NULL OR j.entity = $3)
|
|
45
|
+
ORDER BY j.updated_at ASC
|
|
46
|
+
LIMIT $4
|
|
47
|
+
) q
|
|
33
48
|
`;
|
|
49
|
+
// Bounded by a recent-time window ($1 = within_hours) so this never degenerates
|
|
50
|
+
// into a full-history scan of the HASH-partitioned worker_streams table. The
|
|
51
|
+
// `aid LIKE '%/waiter'` predicate is a suffix wildcard (not index-backed), so
|
|
52
|
+
// the time window is what keeps the intermediate scan small; widening it trades
|
|
53
|
+
// cost for reach. The NOT EXISTS anti-join on hmsh_escalations (workflow_id
|
|
54
|
+
// indexed) is the high-value signal — a suspended waiter with no escalation row.
|
|
34
55
|
const ORPHANED_SIGNALS = (appId) => `
|
|
35
56
|
WITH committed_waiters AS (
|
|
36
57
|
SELECT
|
|
37
|
-
ws.jid
|
|
38
|
-
ws.message->'data'->>'signalId'
|
|
39
|
-
ws.message->'data'->>'queueConfig'
|
|
58
|
+
ws.jid AS job_id,
|
|
59
|
+
ws.message::jsonb->'data'->>'signalId' AS signal_id,
|
|
60
|
+
ws.message::jsonb->'data'->>'queueConfig' AS queue_config,
|
|
40
61
|
ws.workflow_name,
|
|
41
|
-
ws.created_at
|
|
62
|
+
ws.created_at AS suspended_at
|
|
42
63
|
FROM "${appId}".worker_streams ws
|
|
43
64
|
WHERE ws.aid LIKE '%/waiter'
|
|
44
65
|
AND ws.expired_at IS NOT NULL
|
|
45
66
|
AND ws.dead_lettered_at IS NULL
|
|
67
|
+
AND ws.created_at > NOW() - ($1 || ' hours')::INTERVAL
|
|
46
68
|
),
|
|
47
69
|
live_jobs AS (
|
|
48
70
|
SELECT REPLACE(key, 'hmsh:${appId}:j:', '') AS wid
|
|
@@ -54,7 +76,7 @@ const ORPHANED_SIGNALS = (appId) => `
|
|
|
54
76
|
cw.signal_id,
|
|
55
77
|
cw.workflow_name,
|
|
56
78
|
cw.suspended_at,
|
|
57
|
-
EXTRACT(EPOCH FROM (NOW() - cw.suspended_at)) * 1000 AS
|
|
79
|
+
EXTRACT(EPOCH FROM (NOW() - cw.suspended_at)) * 1000 AS waiting_ms,
|
|
58
80
|
cw.queue_config IS NULL AS missing_queue_config
|
|
59
81
|
FROM committed_waiters cw
|
|
60
82
|
JOIN live_jobs lj ON lj.wid = cw.job_id
|
|
@@ -62,11 +84,14 @@ const ORPHANED_SIGNALS = (appId) => `
|
|
|
62
84
|
SELECT 1 FROM public.hmsh_escalations e WHERE e.workflow_id = cw.job_id
|
|
63
85
|
)
|
|
64
86
|
ORDER BY cw.suspended_at ASC
|
|
65
|
-
LIMIT $
|
|
87
|
+
LIMIT $2
|
|
66
88
|
`;
|
|
89
|
+
/** Default cap on events returned in the payload — keeps a high-activity workflow from returning a huge response. */
|
|
90
|
+
const DEFAULT_MAX_EVENTS = 500;
|
|
67
91
|
// ── diagnoseJob ──────────────────────────────────────────────────────────────
|
|
68
|
-
async function diagnoseJob(workflowId, appId = 'durable') {
|
|
92
|
+
async function diagnoseJob(workflowId, appId = 'durable', options = {}) {
|
|
69
93
|
const pool = (0, db_1.getPool)();
|
|
94
|
+
const maxEvents = Math.max(options.maxEvents ?? DEFAULT_MAX_EVENTS, 1);
|
|
70
95
|
let resolved;
|
|
71
96
|
try {
|
|
72
97
|
resolved = await (0, resolve_1.resolveWorkflowHandle)(workflowId);
|
|
@@ -91,9 +116,14 @@ async function diagnoseJob(workflowId, appId = 'durable') {
|
|
|
91
116
|
const isFailed = events.some(e => e.event_type === 'workflow_execution_failed');
|
|
92
117
|
const status = isCompleted ? 'completed' : isFailed ? 'failed' : 'running';
|
|
93
118
|
const lastEventAt = lastEvent?.event_time ?? null;
|
|
94
|
-
const
|
|
119
|
+
const idleMs = status === 'running' && lastEventAt
|
|
95
120
|
? now - new Date(lastEventAt).getTime()
|
|
96
121
|
: null;
|
|
122
|
+
// Cap the events returned in the payload (keep the most recent) so a
|
|
123
|
+
// high-activity workflow can't produce a huge response. Pattern matching
|
|
124
|
+
// still runs over the full in-memory event set.
|
|
125
|
+
const eventsTruncated = events.length > maxEvents;
|
|
126
|
+
const cappedEvents = eventsTruncated ? events.slice(-maxEvents) : events;
|
|
97
127
|
const allMessages = [...workerMessages, ...engineMessages];
|
|
98
128
|
const streamSummary = {
|
|
99
129
|
worker_total: workerMessages.length,
|
|
@@ -104,12 +134,12 @@ async function diagnoseJob(workflowId, appId = 'durable') {
|
|
|
104
134
|
};
|
|
105
135
|
const findings = execution
|
|
106
136
|
? (0, patterns_1.matchPatterns)(execution, workerMessages, engineMessages, escalationRow)
|
|
107
|
-
: [{ condition: 'export_unavailable', confidence: 0.9, severity: 'warning', evidence: ['Execution export unavailable — job may have expired or HotMesh engine is not running'],
|
|
137
|
+
: [{ condition: 'export_unavailable', confidence: 0.9, severity: 'warning', evidence: ['Execution export unavailable — job may have expired or HotMesh engine is not running'], guidance: [] }];
|
|
108
138
|
return {
|
|
109
139
|
workflow_id: workflowId,
|
|
110
140
|
app_id: appId,
|
|
111
141
|
status,
|
|
112
|
-
|
|
142
|
+
idle_for_ms: idleMs,
|
|
113
143
|
workflow_type: resolved.workflowName,
|
|
114
144
|
last_event_at: lastEventAt,
|
|
115
145
|
stream_summary: streamSummary,
|
|
@@ -123,33 +153,40 @@ async function diagnoseJob(workflowId, appId = 'durable') {
|
|
|
123
153
|
created_at: escalationRow?.created_at ?? null,
|
|
124
154
|
},
|
|
125
155
|
findings,
|
|
126
|
-
execution_events:
|
|
156
|
+
execution_events: cappedEvents,
|
|
157
|
+
total_events: events.length,
|
|
158
|
+
events_truncated: eventsTruncated,
|
|
127
159
|
stream_messages: { worker: workerMessages, engine: engineMessages },
|
|
128
160
|
};
|
|
129
161
|
}
|
|
130
162
|
// ── findStalledJobs ──────────────────────────────────────────────────────────
|
|
131
163
|
async function findStalledJobs(params) {
|
|
132
164
|
const appId = params.appId ?? 'durable';
|
|
133
|
-
const
|
|
165
|
+
const idleMinutes = params.idleMinutes ?? 5;
|
|
134
166
|
const limit = Math.min(params.limit ?? 50, 200);
|
|
135
167
|
const keyPrefix = `hmsh:${appId}:j:`;
|
|
168
|
+
// Fail loud: let a query error surface (the api layer maps it to a 500). A
|
|
169
|
+
// silent empty result would hide a missing schema or a broken query.
|
|
136
170
|
const pool = (0, db_1.getPool)();
|
|
137
171
|
const { rows } = await pool.query(STALLED_JOBS(appId), [
|
|
138
172
|
keyPrefix,
|
|
139
|
-
String(
|
|
173
|
+
String(idleMinutes),
|
|
140
174
|
params.workflowType ?? null,
|
|
141
175
|
limit,
|
|
142
|
-
])
|
|
176
|
+
]);
|
|
143
177
|
return { jobs: rows, total: rows.length };
|
|
144
178
|
}
|
|
145
179
|
// ── findOrphanedSignals ──────────────────────────────────────────────────────
|
|
146
180
|
async function findOrphanedSignals(params) {
|
|
147
181
|
const appId = params.appId ?? 'durable';
|
|
182
|
+
const withinHours = Math.min(Math.max(params.withinHours ?? 24, 1), 720);
|
|
148
183
|
const limit = Math.min(params.limit ?? 100, 500);
|
|
184
|
+
// Fail loud: let a query error surface (the api layer maps it to a 500). A
|
|
185
|
+
// silent empty result would hide a missing schema or a broken query — exactly
|
|
186
|
+
// what previously masked a JSON-operator bug on the text `message` column.
|
|
149
187
|
const pool = (0, db_1.getPool)();
|
|
150
|
-
const { rows } = await pool.query(ORPHANED_SIGNALS(appId), [limit])
|
|
151
|
-
|
|
152
|
-
return { orphans: rows, total: rows.length };
|
|
188
|
+
const { rows } = await pool.query(ORPHANED_SIGNALS(appId), [String(withinHours), limit]);
|
|
189
|
+
return { orphans: rows, total: rows.length, within_hours: withinHours };
|
|
153
190
|
}
|
|
154
191
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
155
192
|
function notFound(workflowId, appId) {
|
|
@@ -157,13 +194,15 @@ function notFound(workflowId, appId) {
|
|
|
157
194
|
workflow_id: workflowId,
|
|
158
195
|
app_id: appId,
|
|
159
196
|
status: 'not_found',
|
|
160
|
-
|
|
197
|
+
idle_for_ms: null,
|
|
161
198
|
workflow_type: null,
|
|
162
199
|
last_event_at: null,
|
|
163
200
|
stream_summary: { worker_total: 0, engine_total: 0, dead_lettered: 0, pending: 0, in_flight: 0 },
|
|
164
201
|
escalation: { exists: false, id: null, status: null, signal_key: null, role: null, type: null, created_at: null },
|
|
165
|
-
findings: [{ condition: 'not_found', confidence: 1, severity: 'critical', evidence: ['Workflow ID not found — no task record or job entity'],
|
|
202
|
+
findings: [{ condition: 'not_found', confidence: 1, severity: 'critical', evidence: ['Workflow ID not found — no task record or job entity'], guidance: [] }],
|
|
166
203
|
execution_events: [],
|
|
204
|
+
total_events: 0,
|
|
205
|
+
events_truncated: false,
|
|
167
206
|
stream_messages: { worker: [], engine: [] },
|
|
168
207
|
};
|
|
169
208
|
}
|
|
@@ -5,15 +5,25 @@ export interface Finding {
|
|
|
5
5
|
confidence: number;
|
|
6
6
|
severity: 'critical' | 'warning' | 'info';
|
|
7
7
|
evidence: string[];
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Read-only diagnostic guidance: what the condition means and where a human
|
|
10
|
+
* should look next. This is NOT recovery — HotMesh state only moves forward
|
|
11
|
+
* and cannot be unwound, so guidance never prescribes mutating job state.
|
|
12
|
+
*/
|
|
13
|
+
guidance: Guidance[];
|
|
9
14
|
}
|
|
10
|
-
export interface
|
|
15
|
+
export interface Guidance {
|
|
11
16
|
action: string;
|
|
12
17
|
note: string;
|
|
13
18
|
[key: string]: unknown;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
|
-
* Match event stream + stream messages against known
|
|
21
|
+
* Match event stream + stream messages against known workflow conditions.
|
|
17
22
|
* Returns findings ordered by severity (critical first).
|
|
23
|
+
*
|
|
24
|
+
* Healthy long-waits are a first-class outcome here, not an omission: a
|
|
25
|
+
* workflow suspended at `condition()`/`waitFor()`/`sleepFor()` can sit for days
|
|
26
|
+
* legitimately, so we classify those explicitly rather than treating a frozen
|
|
27
|
+
* job as broken.
|
|
18
28
|
*/
|
|
19
29
|
export declare function matchPatterns(execution: WorkflowExecution, workerMessages: StreamMessage[], engineMessages: StreamMessage[], escalationRow: Record<string, unknown> | null): Finding[];
|