@hailer/mcp 1.1.12 ā 1.1.13
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/CHANGELOG.md +0 -7
- package/{.claude ā dist}/CLAUDE.md +2 -2
- package/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +10 -1
- package/dist/bot/bot-config.js +64 -3
- package/dist/bot/bot-manager.d.ts +2 -0
- package/dist/bot/bot-manager.js +9 -2
- package/dist/bot/bot.d.ts +33 -0
- package/dist/bot/bot.js +461 -160
- package/dist/bot/services/message-classifier.js +17 -0
- package/dist/bot/services/permission-guard.d.ts +52 -0
- package/dist/bot/services/permission-guard.js +149 -0
- package/dist/bot/services/types.d.ts +5 -0
- package/dist/bot/services/typing-indicator.d.ts +6 -1
- package/dist/bot/services/typing-indicator.js +19 -3
- package/dist/cli.js +0 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/UserContextCache.d.ts +5 -0
- package/dist/mcp/UserContextCache.js +51 -19
- package/dist/mcp/hailer-clients.d.ts +19 -1
- package/dist/mcp/hailer-clients.js +158 -24
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +2 -0
- package/dist/mcp/tool-registry.d.ts +17 -4
- package/dist/mcp/tool-registry.js +37 -7
- package/dist/mcp/tools/activity.js +99 -7
- package/dist/mcp/tools/app-scaffold.js +304 -336
- package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
- package/dist/mcp/tools/bot-config/constants.js +94 -0
- package/dist/mcp/tools/bot-config/core.d.ts +253 -0
- package/dist/mcp/tools/bot-config/core.js +2456 -0
- package/dist/mcp/tools/bot-config/index.d.ts +10 -0
- package/dist/mcp/tools/bot-config/index.js +59 -0
- package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
- package/dist/mcp/tools/bot-config/tools.js +15 -0
- package/dist/mcp/tools/bot-config/types.d.ts +50 -0
- package/dist/mcp/tools/bot-config/types.js +6 -0
- package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
- package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
- package/dist/mcp/tools/company.d.ts +9 -0
- package/dist/mcp/tools/company.js +88 -0
- package/dist/mcp/tools/discussion.js +68 -0
- package/dist/mcp/tools/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -0
- package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
- package/dist/mcp/tools/workflow-permissions.js +204 -0
- package/dist/mcp/tools/workflow.js +57 -18
- package/dist/mcp/utils/index.d.ts +2 -0
- package/dist/mcp/utils/index.js +12 -1
- package/dist/mcp/utils/role-utils.d.ts +74 -0
- package/dist/mcp/utils/role-utils.js +151 -0
- package/dist/mcp/utils/types.d.ts +43 -1
- package/dist/mcp/utils/types.js +14 -0
- package/dist/mcp/webhook-handler.d.ts +4 -0
- package/dist/mcp/webhook-handler.js +8 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -127
- package/dist/plugins/vipunen/client.d.ts +150 -0
- package/dist/plugins/vipunen/client.js +535 -0
- package/dist/plugins/vipunen/config/schema-config.json +19 -0
- package/dist/plugins/vipunen/config/schema-doc.json +22 -0
- package/dist/plugins/vipunen/index.d.ts +41 -0
- package/dist/plugins/vipunen/index.js +88 -0
- package/dist/plugins/vipunen/tools.d.ts +26 -0
- package/dist/plugins/vipunen/tools.js +501 -0
- package/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/package.json +2 -1
- package/.claude/agents/agent-ada-skill-builder.md +0 -94
- package/.claude/agents/agent-alejandro-function-fields.md +0 -342
- package/.claude/agents/agent-bjorn-config-audit.md +0 -103
- package/.claude/agents/agent-builder-agent-creator.md +0 -130
- package/.claude/agents/agent-code-simplifier.md +0 -53
- package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
- package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
- package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
- package/.claude/agents/agent-helga-workflow-config.md +0 -204
- package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
- package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
- package/.claude/agents/agent-ivan-monolith.md +0 -154
- package/.claude/agents/agent-kenji-data-reader.md +0 -86
- package/.claude/agents/agent-lars-code-inspector.md +0 -102
- package/.claude/agents/agent-marco-mockup-builder.md +0 -110
- package/.claude/agents/agent-marcus-api-documenter.md +0 -323
- package/.claude/agents/agent-marketplace-publisher.md +0 -280
- package/.claude/agents/agent-marketplace-reviewer.md +0 -309
- package/.claude/agents/agent-permissions-handler.md +0 -208
- package/.claude/agents/agent-simple-writer.md +0 -48
- package/.claude/agents/agent-svetlana-code-review.md +0 -171
- package/.claude/agents/agent-tanya-test-runner.md +0 -333
- package/.claude/agents/agent-ui-designer.md +0 -100
- package/.claude/agents/agent-viktor-sql-insights.md +0 -212
- package/.claude/agents/agent-web-search.md +0 -55
- package/.claude/agents/agent-yevgeni-discussions.md +0 -45
- package/.claude/agents/agent-zara-zapier.md +0 -159
- package/.claude/commands/app-squad.md +0 -135
- package/.claude/commands/audit-squad.md +0 -158
- package/.claude/commands/autoplan.md +0 -563
- package/.claude/commands/cleanup-squad.md +0 -98
- package/.claude/commands/config-squad.md +0 -106
- package/.claude/commands/crud-squad.md +0 -87
- package/.claude/commands/data-squad.md +0 -97
- package/.claude/commands/debug-squad.md +0 -303
- package/.claude/commands/doc-squad.md +0 -65
- package/.claude/commands/handoff.md +0 -137
- package/.claude/commands/health.md +0 -49
- package/.claude/commands/help.md +0 -29
- package/.claude/commands/help:agents.md +0 -151
- package/.claude/commands/help:commands.md +0 -78
- package/.claude/commands/help:faq.md +0 -79
- package/.claude/commands/help:plugins.md +0 -50
- package/.claude/commands/help:skills.md +0 -93
- package/.claude/commands/help:tools.md +0 -75
- package/.claude/commands/hotfix-squad.md +0 -112
- package/.claude/commands/integration-squad.md +0 -82
- package/.claude/commands/janitor-squad.md +0 -167
- package/.claude/commands/learn-auto.md +0 -120
- package/.claude/commands/learn.md +0 -120
- package/.claude/commands/mcp-list.md +0 -27
- package/.claude/commands/onboard-squad.md +0 -140
- package/.claude/commands/plan-workspace.md +0 -732
- package/.claude/commands/prd.md +0 -130
- package/.claude/commands/project-status.md +0 -82
- package/.claude/commands/publish.md +0 -138
- package/.claude/commands/recap.md +0 -69
- package/.claude/commands/restore.md +0 -64
- package/.claude/commands/review-squad.md +0 -152
- package/.claude/commands/save.md +0 -24
- package/.claude/commands/stats.md +0 -19
- package/.claude/commands/swarm.md +0 -210
- package/.claude/commands/tool-builder.md +0 -39
- package/.claude/commands/ws-pull.md +0 -44
- package/.claude/hooks/_shared-memory.cjs +0 -305
- package/.claude/hooks/_utils.cjs +0 -108
- package/.claude/hooks/agent-failure-detector.cjs +0 -383
- package/.claude/hooks/agent-usage-logger.cjs +0 -204
- package/.claude/hooks/app-edit-guard.cjs +0 -494
- package/.claude/hooks/auto-learn.cjs +0 -304
- package/.claude/hooks/bash-guard.cjs +0 -272
- package/.claude/hooks/builder-mode-manager.cjs +0 -354
- package/.claude/hooks/bulk-activity-guard.cjs +0 -271
- package/.claude/hooks/context-watchdog.cjs +0 -230
- package/.claude/hooks/delegation-reminder.cjs +0 -465
- package/.claude/hooks/design-system-lint.cjs +0 -271
- package/.claude/hooks/post-scaffold-hook.cjs +0 -181
- package/.claude/hooks/prompt-guard.cjs +0 -354
- package/.claude/hooks/publish-template-guard.cjs +0 -147
- package/.claude/hooks/session-start.cjs +0 -35
- package/.claude/hooks/shared-memory-writer.cjs +0 -147
- package/.claude/hooks/skill-injector.cjs +0 -140
- package/.claude/hooks/skill-usage-logger.cjs +0 -258
- package/.claude/hooks/src-edit-guard.cjs +0 -240
- package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
- package/.claude/settings.json +0 -257
- package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
- package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
- package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
- package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
- package/.claude/skills/agent-structure/SKILL.md +0 -98
- package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
- package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
- package/.claude/skills/delegation-routing/SKILL.md +0 -202
- package/.claude/skills/frontend-design/SKILL.md +0 -254
- package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
- package/.claude/skills/hailer-api-client/SKILL.md +0 -518
- package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
- package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
- package/.claude/skills/hailer-design-system/SKILL.md +0 -235
- package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
- package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
- package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
- package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
- package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
- package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
- package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
- package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
- package/.claude/skills/integration-patterns/SKILL.md +0 -421
- package/.claude/skills/json-only-output/SKILL.md +0 -72
- package/.claude/skills/lsp-setup/SKILL.md +0 -160
- package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
- package/.claude/skills/optional-parameters/SKILL.md +0 -72
- package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
- package/.claude/skills/testing-patterns/SKILL.md +0 -630
- package/.claude/skills/tool-builder/SKILL.md +0 -250
- package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
- package/.claude/skills/tool-response-verification/SKILL.md +0 -92
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
- package/.mcp.json +0 -13
- package/.opencode/agent/agent-ada-skill-builder.md +0 -35
- package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
- package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
- package/.opencode/agent/agent-builder-agent-creator.md +0 -39
- package/.opencode/agent/agent-code-simplifier.md +0 -31
- package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
- package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
- package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
- package/.opencode/agent/agent-helga-workflow-config.md +0 -203
- package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
- package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
- package/.opencode/agent/agent-ivan-monolith.md +0 -46
- package/.opencode/agent/agent-kenji-data-reader.md +0 -53
- package/.opencode/agent/agent-lars-code-inspector.md +0 -28
- package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
- package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
- package/.opencode/agent/agent-marketplace-publisher.md +0 -44
- package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
- package/.opencode/agent/agent-permissions-handler.md +0 -50
- package/.opencode/agent/agent-simple-writer.md +0 -45
- package/.opencode/agent/agent-svetlana-code-review.md +0 -39
- package/.opencode/agent/agent-tanya-test-runner.md +0 -57
- package/.opencode/agent/agent-ui-designer.md +0 -56
- package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
- package/.opencode/agent/agent-web-search.md +0 -42
- package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
- package/.opencode/agent/agent-zara-zapier.md +0 -53
- package/.opencode/commands/app-squad.md +0 -135
- package/.opencode/commands/audit-squad.md +0 -158
- package/.opencode/commands/autoplan.md +0 -563
- package/.opencode/commands/cleanup-squad.md +0 -98
- package/.opencode/commands/config-squad.md +0 -106
- package/.opencode/commands/crud-squad.md +0 -87
- package/.opencode/commands/data-squad.md +0 -97
- package/.opencode/commands/debug-squad.md +0 -303
- package/.opencode/commands/doc-squad.md +0 -65
- package/.opencode/commands/handoff.md +0 -137
- package/.opencode/commands/health.md +0 -49
- package/.opencode/commands/help-agents.md +0 -151
- package/.opencode/commands/help-commands.md +0 -32
- package/.opencode/commands/help-faq.md +0 -29
- package/.opencode/commands/help-plugins.md +0 -28
- package/.opencode/commands/help-skills.md +0 -7
- package/.opencode/commands/help-tools.md +0 -40
- package/.opencode/commands/help.md +0 -28
- package/.opencode/commands/hotfix-squad.md +0 -112
- package/.opencode/commands/integration-squad.md +0 -82
- package/.opencode/commands/janitor-squad.md +0 -167
- package/.opencode/commands/learn-auto.md +0 -120
- package/.opencode/commands/learn.md +0 -120
- package/.opencode/commands/mcp-list.md +0 -27
- package/.opencode/commands/onboard-squad.md +0 -140
- package/.opencode/commands/plan-workspace.md +0 -732
- package/.opencode/commands/prd.md +0 -131
- package/.opencode/commands/project-status.md +0 -82
- package/.opencode/commands/publish.md +0 -138
- package/.opencode/commands/recap.md +0 -69
- package/.opencode/commands/restore.md +0 -64
- package/.opencode/commands/review-squad.md +0 -152
- package/.opencode/commands/save.md +0 -24
- package/.opencode/commands/stats.md +0 -19
- package/.opencode/commands/swarm.md +0 -210
- package/.opencode/commands/tool-builder.md +0 -39
- package/.opencode/commands/ws-pull.md +0 -44
- package/.opencode/opencode.json +0 -28
- package/SESSION-HANDOFF.md +0 -68
- package/inbox/2026-03-04-bot-config-patterns.md +0 -24
- package/scripts/postinstall.cjs +0 -64
- package/scripts/test-hal-tools.ts +0 -154
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Document Tools - PDF/CSV Generation
|
|
4
|
+
*
|
|
5
|
+
* Tools for generating documents from templates:
|
|
6
|
+
* - List document templates (READ)
|
|
7
|
+
* - Generate PDF documents from activities (WRITE)
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.generateDocumentTool = exports.listDocumentTemplatesTool = void 0;
|
|
44
|
+
const zod_1 = require("zod");
|
|
45
|
+
const tool_registry_1 = require("../tool-registry");
|
|
46
|
+
const index_1 = require("../utils/index");
|
|
47
|
+
const request_logger_1 = require("../../lib/request-logger");
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const logger = (0, index_1.createLogger)({ component: 'document-tools' });
|
|
52
|
+
// Helper: extract error message from various error types
|
|
53
|
+
function extractErrorMessage(error) {
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
return error.message;
|
|
56
|
+
}
|
|
57
|
+
else if (error && typeof error === 'object') {
|
|
58
|
+
if ('message' in error)
|
|
59
|
+
return String(error.message);
|
|
60
|
+
if ('error' in error) {
|
|
61
|
+
const e = error.error;
|
|
62
|
+
return typeof e === 'string' ? e : JSON.stringify(e);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(error);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return '[Complex error object]';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return String(error);
|
|
72
|
+
}
|
|
73
|
+
function convertFiltersToApiFormat(mcpFilters) {
|
|
74
|
+
const apiFilters = { and: [] };
|
|
75
|
+
for (const [fieldId, filter] of Object.entries(mcpFilters)) {
|
|
76
|
+
switch (filter.operator) {
|
|
77
|
+
case 'text_search':
|
|
78
|
+
if (filter.value !== undefined) {
|
|
79
|
+
apiFilters.and.push({ [fieldId]: { textSearch: filter.value } });
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'equals':
|
|
83
|
+
if (filter.value !== undefined) {
|
|
84
|
+
apiFilters.and.push({ [fieldId]: { equalTo: filter.value } });
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case 'not_equals':
|
|
88
|
+
if (filter.value !== undefined) {
|
|
89
|
+
apiFilters.and.push({ [fieldId]: { notEqualTo: filter.value } });
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case 'contains':
|
|
93
|
+
if (filter.value !== undefined) {
|
|
94
|
+
apiFilters.and.push({ [fieldId]: { contains: filter.value } });
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case 'greater_than':
|
|
98
|
+
if (filter.value !== undefined && typeof filter.value === 'number') {
|
|
99
|
+
apiFilters.and.push({ [fieldId]: { greaterThan: filter.value } });
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case 'less_than':
|
|
103
|
+
if (filter.value !== undefined && typeof filter.value === 'number') {
|
|
104
|
+
apiFilters.and.push({ [fieldId]: { lessThan: filter.value } });
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case 'range':
|
|
108
|
+
if (filter.start !== undefined && filter.end !== undefined) {
|
|
109
|
+
apiFilters.and.push({ [fieldId]: { between: [String(filter.start), String(filter.end)] } });
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
// Unknown operator, try text_search as fallback
|
|
114
|
+
if (filter.value !== undefined) {
|
|
115
|
+
apiFilters.and.push({ [fieldId]: { textSearch: filter.value } });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return apiFilters;
|
|
120
|
+
}
|
|
121
|
+
// Helper: delay for rate limiting and retry backoff
|
|
122
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
123
|
+
// Retry configuration
|
|
124
|
+
const RETRY_CONFIG = {
|
|
125
|
+
maxRetries: 3,
|
|
126
|
+
baseDelayMs: 1000, // 1 second base delay
|
|
127
|
+
batchDelayMs: 500, // 500ms between batches
|
|
128
|
+
retryableCodes: [502, 503, 504, 429] // Gateway errors and rate limits
|
|
129
|
+
};
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// TOOL 1: LIST DOCUMENT TEMPLATES
|
|
132
|
+
// ============================================================================
|
|
133
|
+
const listDocumentTemplatesDescription = `List available document templates for PDF/CSV generation
|
|
134
|
+
|
|
135
|
+
**Returns**: Template IDs, names, types, and associated workflow IDs
|
|
136
|
+
|
|
137
|
+
**Example**:
|
|
138
|
+
\`\`\`javascript
|
|
139
|
+
list_document_templates({ workflowId: "691ffdf84217e9e8434e5694" })
|
|
140
|
+
\`\`\``;
|
|
141
|
+
exports.listDocumentTemplatesTool = {
|
|
142
|
+
name: 'list_document_templates',
|
|
143
|
+
group: tool_registry_1.ToolGroup.READ,
|
|
144
|
+
description: listDocumentTemplatesDescription,
|
|
145
|
+
schema: zod_1.z.object({
|
|
146
|
+
workflowId: zod_1.z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("Optional workflow ID to filter templates for a specific workflow")
|
|
150
|
+
}),
|
|
151
|
+
async execute(args, context) {
|
|
152
|
+
logger.debug('Listing document templates', {
|
|
153
|
+
workflowId: args.workflowId,
|
|
154
|
+
apiKey: context.apiKey.substring(0, 8) + '...'
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
157
|
+
const rawInit = context.init;
|
|
158
|
+
let templates = [];
|
|
159
|
+
// Method 1: Check workspace-level templates
|
|
160
|
+
if (rawInit.templates) {
|
|
161
|
+
const workspaceTemplates = Array.isArray(rawInit.templates)
|
|
162
|
+
? rawInit.templates
|
|
163
|
+
: Object.values(rawInit.templates);
|
|
164
|
+
templates.push(...workspaceTemplates);
|
|
165
|
+
}
|
|
166
|
+
if (rawInit.documentTemplates) {
|
|
167
|
+
const docTemplates = Array.isArray(rawInit.documentTemplates)
|
|
168
|
+
? rawInit.documentTemplates
|
|
169
|
+
: Object.values(rawInit.documentTemplates);
|
|
170
|
+
templates.push(...docTemplates);
|
|
171
|
+
}
|
|
172
|
+
// Method 2: Check per-workflow templates (in processes)
|
|
173
|
+
if (rawInit.processes) {
|
|
174
|
+
const processes = Array.isArray(rawInit.processes)
|
|
175
|
+
? rawInit.processes
|
|
176
|
+
: Object.values(rawInit.processes);
|
|
177
|
+
for (const process of processes) {
|
|
178
|
+
// Check templates in process
|
|
179
|
+
if (process.templates) {
|
|
180
|
+
const procTemplates = Array.isArray(process.templates)
|
|
181
|
+
? process.templates
|
|
182
|
+
: Object.values(process.templates);
|
|
183
|
+
// Add workflowId to each template
|
|
184
|
+
for (const t of procTemplates) {
|
|
185
|
+
templates.push({ ...t, workflowId: process._id, workflowName: process.name });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Check documentTemplates in process
|
|
189
|
+
if (process.documentTemplates) {
|
|
190
|
+
const procDocTemplates = Array.isArray(process.documentTemplates)
|
|
191
|
+
? process.documentTemplates
|
|
192
|
+
: Object.values(process.documentTemplates);
|
|
193
|
+
for (const t of procDocTemplates) {
|
|
194
|
+
templates.push({ ...t, workflowId: process._id, workflowName: process.name });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Check printTemplates in process
|
|
198
|
+
if (process.printTemplates) {
|
|
199
|
+
const printTemplates = Array.isArray(process.printTemplates)
|
|
200
|
+
? process.printTemplates
|
|
201
|
+
: Object.values(process.printTemplates);
|
|
202
|
+
for (const t of printTemplates) {
|
|
203
|
+
templates.push({ ...t, workflowId: process._id, workflowName: process.name });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Method 3: Try dedicated API call if no templates found
|
|
209
|
+
if (templates.length === 0) {
|
|
210
|
+
try {
|
|
211
|
+
// Try v3.template.list or similar
|
|
212
|
+
const apiTemplates = await context.hailer.request('v3.documentTemplate.list', []);
|
|
213
|
+
if (Array.isArray(apiTemplates)) {
|
|
214
|
+
templates.push(...apiTemplates);
|
|
215
|
+
}
|
|
216
|
+
else if (apiTemplates?.templates) {
|
|
217
|
+
templates.push(...apiTemplates.templates);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (apiError) {
|
|
221
|
+
logger.debug('v3.documentTemplate.list not available', { error: apiError });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
logger.debug('Template discovery results', {
|
|
225
|
+
initKeys: Object.keys(rawInit),
|
|
226
|
+
processCount: rawInit.processes ? Object.keys(rawInit.processes).length : 0,
|
|
227
|
+
templatesFound: templates.length
|
|
228
|
+
});
|
|
229
|
+
// Filter by workflow if provided
|
|
230
|
+
if (args.workflowId) {
|
|
231
|
+
templates = templates.filter((t) => t.workflowId === args.workflowId ||
|
|
232
|
+
t.processId === args.workflowId ||
|
|
233
|
+
t.pid === args.workflowId);
|
|
234
|
+
}
|
|
235
|
+
if (templates.length === 0) {
|
|
236
|
+
// Return diagnostic info to help debug
|
|
237
|
+
const processInfo = rawInit.processes
|
|
238
|
+
? Object.entries(rawInit.processes).slice(0, 3).map(([id, p]) => ({
|
|
239
|
+
id,
|
|
240
|
+
name: p.name,
|
|
241
|
+
hasTemplates: !!p.templates,
|
|
242
|
+
hasDocumentTemplates: !!p.documentTemplates,
|
|
243
|
+
hasPrintTemplates: !!p.printTemplates,
|
|
244
|
+
keys: Object.keys(p).filter(k => k.toLowerCase().includes('template'))
|
|
245
|
+
}))
|
|
246
|
+
: [];
|
|
247
|
+
return {
|
|
248
|
+
content: [{
|
|
249
|
+
type: "text",
|
|
250
|
+
text: `š No document templates found${args.workflowId ? ` for workflow ${args.workflowId}` : ''}.\n\n` +
|
|
251
|
+
`**Init data keys**: ${Object.keys(rawInit).join(', ')}\n\n` +
|
|
252
|
+
`**Process sample** (first 3):\n${JSON.stringify(processInfo, null, 2)}\n\n` +
|
|
253
|
+
`š” Document templates may need to be created in Hailer UI first.`
|
|
254
|
+
}]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
let responseText = `š **Document Templates** (${templates.length} total):\n\n`;
|
|
258
|
+
templates.forEach((template, index) => {
|
|
259
|
+
// Get template ID from various possible fields
|
|
260
|
+
const templateId = template._id || template.id || template.templateId || template.key || Object.keys(template)[0];
|
|
261
|
+
const templateName = template.name || template.label || template.title || 'Untitled';
|
|
262
|
+
responseText += `${index + 1}. **${templateName}**\n`;
|
|
263
|
+
responseText += ` - Template ID: \`${templateId}\`\n`;
|
|
264
|
+
if (template.type)
|
|
265
|
+
responseText += ` - Type: ${template.type}\n`;
|
|
266
|
+
if (template.workflowId || template.processId) {
|
|
267
|
+
responseText += ` - Workflow: ${template.workflowName || ''} (\`${template.workflowId || template.processId}\`)\n`;
|
|
268
|
+
}
|
|
269
|
+
responseText += `\n`;
|
|
270
|
+
});
|
|
271
|
+
responseText += `\nš” **Usage**: Use \`generate_document\` with a template ID and activity IDs to create PDF documents.`;
|
|
272
|
+
return {
|
|
273
|
+
content: [{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: responseText
|
|
276
|
+
}]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (!request_logger_1.RequestLogger.getCurrent())
|
|
281
|
+
logger.error("Failed to list document templates", error);
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: `ā Failed to list document templates: ${error instanceof Error ? error.message : String(error)}\n\n` +
|
|
286
|
+
`š” Document templates may not be available in this workspace.`
|
|
287
|
+
}]
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// TOOL 2: GENERATE DOCUMENT
|
|
294
|
+
// ============================================================================
|
|
295
|
+
const generateDocumentDescription = `Generate PDF document from template and activities, upload to Hailer
|
|
296
|
+
|
|
297
|
+
**Two modes:**
|
|
298
|
+
1. **Direct IDs**: Pass \`activityIds\` array directly
|
|
299
|
+
2. **Filter-based**: Pass \`workflowId\` + optional filters (fetches IDs internally - saves context!)
|
|
300
|
+
|
|
301
|
+
**Auto-batching**: For large activity lists (>50), automatically splits into batches to avoid timeouts.
|
|
302
|
+
|
|
303
|
+
**Parameters**:
|
|
304
|
+
- \`templateId\`: Document template ID (from list_document_templates)
|
|
305
|
+
- \`activityIds\`: Array of activity IDs (OR use workflowId + filters)
|
|
306
|
+
- \`workflowId\`: Workflow to fetch activities from (alternative to activityIds)
|
|
307
|
+
- \`phaseId\`: Optional phase filter (used with workflowId)
|
|
308
|
+
- \`filters\`: Optional field filters, same format as list_activities
|
|
309
|
+
- \`limit\`: Max activities to process (default: 1000)
|
|
310
|
+
- \`discussionId\`: Optional - post PDF(s) to this discussion
|
|
311
|
+
- \`batchSize\`: Optional - activities per PDF (default: 50)
|
|
312
|
+
|
|
313
|
+
**Example with filters (recommended for large datasets):**
|
|
314
|
+
\`\`\`javascript
|
|
315
|
+
generate_document({
|
|
316
|
+
templateId: "abc123",
|
|
317
|
+
workflowId: "workflow-id",
|
|
318
|
+
phaseId: "phase-id",
|
|
319
|
+
filters: { "fieldId": { "operator": "equals", "value": "Defender" }},
|
|
320
|
+
discussionId: "discussion-id"
|
|
321
|
+
})
|
|
322
|
+
\`\`\`
|
|
323
|
+
|
|
324
|
+
**Example with direct IDs:**
|
|
325
|
+
\`\`\`javascript
|
|
326
|
+
generate_document({
|
|
327
|
+
templateId: "abc123",
|
|
328
|
+
activityIds: ["id1", "id2"],
|
|
329
|
+
discussionId: "discussion-id"
|
|
330
|
+
})
|
|
331
|
+
\`\`\``;
|
|
332
|
+
exports.generateDocumentTool = {
|
|
333
|
+
name: 'generate_document',
|
|
334
|
+
group: tool_registry_1.ToolGroup.WRITE,
|
|
335
|
+
description: generateDocumentDescription,
|
|
336
|
+
schema: zod_1.z.object({
|
|
337
|
+
templateId: zod_1.z
|
|
338
|
+
.string()
|
|
339
|
+
.min(1, "Template ID is required")
|
|
340
|
+
.describe("Document template ID (use list_document_templates to find available templates)"),
|
|
341
|
+
// Option 1: Direct activity IDs
|
|
342
|
+
activityIds: zod_1.z
|
|
343
|
+
.preprocess((val) => {
|
|
344
|
+
if (typeof val === 'string') {
|
|
345
|
+
try {
|
|
346
|
+
return JSON.parse(val);
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return val.split(',').map(id => id.trim()).filter(Boolean);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return val;
|
|
353
|
+
}, zod_1.z.array(zod_1.z.string()))
|
|
354
|
+
.optional()
|
|
355
|
+
.describe("Array of activity IDs (OR use workflowId + filters instead)"),
|
|
356
|
+
// Option 2: Filter-based (fetches IDs internally)
|
|
357
|
+
workflowId: zod_1.z
|
|
358
|
+
.string()
|
|
359
|
+
.optional()
|
|
360
|
+
.describe("Workflow ID to fetch activities from (alternative to activityIds)"),
|
|
361
|
+
phaseId: zod_1.z
|
|
362
|
+
.string()
|
|
363
|
+
.optional()
|
|
364
|
+
.describe("Optional phase ID filter (used with workflowId)"),
|
|
365
|
+
filters: zod_1.z
|
|
366
|
+
.any()
|
|
367
|
+
.optional()
|
|
368
|
+
.describe("Field filters, same format as list_activities: { fieldId: { operator, value }}"),
|
|
369
|
+
limit: zod_1.z
|
|
370
|
+
.number()
|
|
371
|
+
.optional()
|
|
372
|
+
.default(1000)
|
|
373
|
+
.describe("Max activities to fetch when using workflowId (default: 1000)"),
|
|
374
|
+
// Common options
|
|
375
|
+
discussionId: zod_1.z
|
|
376
|
+
.string()
|
|
377
|
+
.optional()
|
|
378
|
+
.describe("Optional discussion ID - if provided, the PDF(s) will be posted to this discussion"),
|
|
379
|
+
timeZone: zod_1.z
|
|
380
|
+
.string()
|
|
381
|
+
.optional()
|
|
382
|
+
.default("Europe/Helsinki")
|
|
383
|
+
.describe("Timezone for date formatting in document (default: Europe/Helsinki)"),
|
|
384
|
+
batchSize: zod_1.z
|
|
385
|
+
.number()
|
|
386
|
+
.optional()
|
|
387
|
+
.default(50)
|
|
388
|
+
.describe("Activities per PDF batch (default: 50). Large lists are auto-batched to avoid timeouts.")
|
|
389
|
+
}),
|
|
390
|
+
async execute(args, context) {
|
|
391
|
+
const batchSize = args.batchSize || 50;
|
|
392
|
+
const limit = args.limit || 1000;
|
|
393
|
+
let activityIds = args.activityIds || [];
|
|
394
|
+
// Validate: need either activityIds or workflowId
|
|
395
|
+
if (activityIds.length === 0 && !args.workflowId) {
|
|
396
|
+
return {
|
|
397
|
+
content: [{
|
|
398
|
+
type: "text",
|
|
399
|
+
text: `ā Missing required parameter: provide either \`activityIds\` or \`workflowId\`.\n\n` +
|
|
400
|
+
`**Option 1 - Direct IDs:**\n` +
|
|
401
|
+
`\`\`\`json\n{ "templateId": "...", "activityIds": ["id1", "id2"] }\n\`\`\`\n\n` +
|
|
402
|
+
`**Option 2 - Filter-based (recommended for large datasets):**\n` +
|
|
403
|
+
`\`\`\`json\n{ "templateId": "...", "workflowId": "...", "phaseId": "...", "filters": {...} }\n\`\`\``
|
|
404
|
+
}]
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
// If workflowId provided, fetch activity IDs internally
|
|
408
|
+
if (args.workflowId && activityIds.length === 0) {
|
|
409
|
+
logger.debug('Fetching activities via filters', {
|
|
410
|
+
workflowId: args.workflowId,
|
|
411
|
+
phaseId: args.phaseId,
|
|
412
|
+
hasFilters: !!args.filters,
|
|
413
|
+
limit
|
|
414
|
+
});
|
|
415
|
+
try {
|
|
416
|
+
// Get phases if phaseId not provided - we need at least one phase
|
|
417
|
+
let phaseIds = [];
|
|
418
|
+
if (args.phaseId) {
|
|
419
|
+
phaseIds = [args.phaseId];
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
// Get all phases for this workflow
|
|
423
|
+
const workflow = context.init.processes.find((p) => p._id === args.workflowId);
|
|
424
|
+
if (workflow?.phases) {
|
|
425
|
+
phaseIds = Object.keys(workflow.phases);
|
|
426
|
+
}
|
|
427
|
+
if (phaseIds.length === 0) {
|
|
428
|
+
throw new Error(`No phases found for workflow ${args.workflowId}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Convert filters to API format if provided
|
|
432
|
+
let apiFilters;
|
|
433
|
+
if (args.filters && typeof args.filters === 'object') {
|
|
434
|
+
apiFilters = convertFiltersToApiFormat(args.filters);
|
|
435
|
+
logger.debug('Converted filters to API format', {
|
|
436
|
+
original: args.filters,
|
|
437
|
+
converted: apiFilters
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Fetch activities from each phase with pagination
|
|
441
|
+
const allIds = [];
|
|
442
|
+
const PAGE_SIZE = 200; // Fetch in pages of 200
|
|
443
|
+
for (const phaseId of phaseIds) {
|
|
444
|
+
if (allIds.length >= limit)
|
|
445
|
+
break;
|
|
446
|
+
let page = 0;
|
|
447
|
+
let hasMore = true;
|
|
448
|
+
// Paginate through all results for this phase
|
|
449
|
+
while (hasMore && allIds.length < limit) {
|
|
450
|
+
const remaining = limit - allIds.length;
|
|
451
|
+
const result = await context.hailer.fetchActivityList(args.workflowId, phaseId, Math.min(remaining, PAGE_SIZE), {
|
|
452
|
+
page,
|
|
453
|
+
filters: apiFilters,
|
|
454
|
+
returnFlat: true,
|
|
455
|
+
includeStats: true
|
|
456
|
+
// Don't pass fields - API returns _id by default
|
|
457
|
+
});
|
|
458
|
+
// Extract IDs from result
|
|
459
|
+
const activities = result?.activities || result?.list || result?.data || [];
|
|
460
|
+
const metadata = result?.metadata || {};
|
|
461
|
+
for (const activity of activities) {
|
|
462
|
+
if (allIds.length >= limit)
|
|
463
|
+
break;
|
|
464
|
+
if (activity._id) {
|
|
465
|
+
allIds.push(activity._id);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
logger.debug(`Fetched page ${page + 1} from phase ${phaseId}`, {
|
|
469
|
+
phaseId,
|
|
470
|
+
page: page + 1,
|
|
471
|
+
fetched: activities.length,
|
|
472
|
+
totalInPhase: metadata.totalCount,
|
|
473
|
+
totalSoFar: allIds.length
|
|
474
|
+
});
|
|
475
|
+
// Check if there are more pages
|
|
476
|
+
const totalInPhase = metadata.totalCount || 0;
|
|
477
|
+
const fetchedSoFarInPhase = (page + 1) * PAGE_SIZE;
|
|
478
|
+
hasMore = activities.length === PAGE_SIZE && fetchedSoFarInPhase < totalInPhase;
|
|
479
|
+
page++;
|
|
480
|
+
// Safety: max 50 pages (10,000 activities per phase)
|
|
481
|
+
if (page >= 50) {
|
|
482
|
+
logger.warn('Hit pagination safety limit', { phaseId, page });
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
activityIds = allIds;
|
|
488
|
+
if (activityIds.length === 0) {
|
|
489
|
+
return {
|
|
490
|
+
content: [{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: `š No activities found matching the filters.\n\n` +
|
|
493
|
+
`**Workflow:** ${args.workflowId}\n` +
|
|
494
|
+
`**Phase:** ${args.phaseId || 'all phases'}\n` +
|
|
495
|
+
`**Filters:** ${args.filters ? JSON.stringify(args.filters) : 'none'}\n\n` +
|
|
496
|
+
`š” Try adjusting your filters or check that activities exist in this workflow.`
|
|
497
|
+
}]
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
logger.debug('Activity IDs fetched via filters', {
|
|
501
|
+
count: activityIds.length,
|
|
502
|
+
limit,
|
|
503
|
+
phaseCount: phaseIds.length
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
catch (fetchError) {
|
|
507
|
+
const errorMsg = extractErrorMessage(fetchError);
|
|
508
|
+
logger.error('Failed to fetch activities via filters', { error: errorMsg });
|
|
509
|
+
return {
|
|
510
|
+
content: [{
|
|
511
|
+
type: "text",
|
|
512
|
+
text: `ā Failed to fetch activities: ${errorMsg}\n\n` +
|
|
513
|
+
`**Workflow:** ${args.workflowId}\n` +
|
|
514
|
+
`**Phase:** ${args.phaseId || 'all phases'}\n` +
|
|
515
|
+
`**Filters:** ${args.filters ? JSON.stringify(args.filters) : 'none'}\n\n` +
|
|
516
|
+
`š” Check that:\n` +
|
|
517
|
+
`- Workflow and phase IDs are correct\n` +
|
|
518
|
+
`- Filter field IDs exist in the workflow\n` +
|
|
519
|
+
`- Filter format is correct: { "fieldId": { "operator": "text_search", "value": "..." }}`
|
|
520
|
+
}]
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const totalActivities = activityIds.length;
|
|
525
|
+
logger.debug('Generating document', {
|
|
526
|
+
templateId: args.templateId,
|
|
527
|
+
activityCount: totalActivities,
|
|
528
|
+
batchSize,
|
|
529
|
+
batchCount: Math.ceil(totalActivities / batchSize),
|
|
530
|
+
hasDiscussionId: !!args.discussionId,
|
|
531
|
+
timeZone: args.timeZone,
|
|
532
|
+
mode: args.workflowId ? 'filter-based' : 'direct-ids'
|
|
533
|
+
});
|
|
534
|
+
try {
|
|
535
|
+
// Split activities into batches
|
|
536
|
+
const batches = [];
|
|
537
|
+
for (let i = 0; i < activityIds.length; i += batchSize) {
|
|
538
|
+
batches.push(activityIds.slice(i, i + batchSize));
|
|
539
|
+
}
|
|
540
|
+
const results = [];
|
|
541
|
+
const errors = [];
|
|
542
|
+
// Process each batch with retry logic and delays
|
|
543
|
+
for (let batchNum = 0; batchNum < batches.length; batchNum++) {
|
|
544
|
+
const batch = batches[batchNum];
|
|
545
|
+
// Add delay between batches (except first one)
|
|
546
|
+
if (batchNum > 0) {
|
|
547
|
+
await delay(RETRY_CONFIG.batchDelayMs);
|
|
548
|
+
}
|
|
549
|
+
logger.debug(`Processing batch ${batchNum + 1}/${batches.length}`, {
|
|
550
|
+
batchNum: batchNum + 1,
|
|
551
|
+
activityCount: batch.length
|
|
552
|
+
});
|
|
553
|
+
let lastError = '';
|
|
554
|
+
let success = false;
|
|
555
|
+
// Retry loop for this batch
|
|
556
|
+
for (let attempt = 0; attempt < RETRY_CONFIG.maxRetries && !success; attempt++) {
|
|
557
|
+
if (attempt > 0) {
|
|
558
|
+
// Exponential backoff: 1s, 2s, 4s
|
|
559
|
+
const backoffMs = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt - 1);
|
|
560
|
+
logger.debug(`Retry attempt ${attempt + 1} for batch ${batchNum + 1} after ${backoffMs}ms`);
|
|
561
|
+
await delay(backoffMs);
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
// Build templateMap for this batch
|
|
565
|
+
const templateMap = { [args.templateId]: batch };
|
|
566
|
+
const requestBody = {
|
|
567
|
+
templateMap,
|
|
568
|
+
timeZone: args.timeZone || "Europe/Helsinki",
|
|
569
|
+
save: 0
|
|
570
|
+
};
|
|
571
|
+
// Call REST API to generate PDF (binary response)
|
|
572
|
+
const pdfBuffer = await context.hailer.callRest({
|
|
573
|
+
operation: 'generate_document',
|
|
574
|
+
endpoint: '/documentgenerator',
|
|
575
|
+
method: 'POST',
|
|
576
|
+
body: requestBody,
|
|
577
|
+
timeout: 120000, // 2 minutes per batch
|
|
578
|
+
responseType: 'binary'
|
|
579
|
+
});
|
|
580
|
+
// Convert buffer to Buffer instance if needed
|
|
581
|
+
let buffer;
|
|
582
|
+
if (typeof pdfBuffer === 'string') {
|
|
583
|
+
buffer = Buffer.from(pdfBuffer, 'base64');
|
|
584
|
+
}
|
|
585
|
+
else if (Buffer.isBuffer(pdfBuffer)) {
|
|
586
|
+
buffer = pdfBuffer;
|
|
587
|
+
}
|
|
588
|
+
else if (pdfBuffer instanceof ArrayBuffer) {
|
|
589
|
+
buffer = Buffer.from(pdfBuffer);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
throw new Error('Unexpected PDF response format');
|
|
593
|
+
}
|
|
594
|
+
// Save to temporary file
|
|
595
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
596
|
+
const filename = batches.length > 1
|
|
597
|
+
? `document-batch${batchNum + 1}-${timestamp}.pdf`
|
|
598
|
+
: `document-${timestamp}.pdf`;
|
|
599
|
+
const tempDir = os.tmpdir();
|
|
600
|
+
const tempPath = path.join(tempDir, filename);
|
|
601
|
+
fs.writeFileSync(tempPath, buffer);
|
|
602
|
+
// Upload PDF to Hailer (also with retry for upload failures)
|
|
603
|
+
let uploadResult;
|
|
604
|
+
for (let uploadAttempt = 0; uploadAttempt < RETRY_CONFIG.maxRetries; uploadAttempt++) {
|
|
605
|
+
if (uploadAttempt > 0) {
|
|
606
|
+
const backoffMs = RETRY_CONFIG.baseDelayMs * Math.pow(2, uploadAttempt - 1);
|
|
607
|
+
logger.debug(`Retry upload attempt ${uploadAttempt + 1} after ${backoffMs}ms`);
|
|
608
|
+
await delay(backoffMs);
|
|
609
|
+
}
|
|
610
|
+
uploadResult = await context.hailer.uploadFile({
|
|
611
|
+
path: tempPath,
|
|
612
|
+
filename
|
|
613
|
+
});
|
|
614
|
+
if (uploadResult.success)
|
|
615
|
+
break;
|
|
616
|
+
// Check if error is retryable
|
|
617
|
+
const isRetryable = RETRY_CONFIG.retryableCodes.some(code => uploadResult.error?.includes(String(code)));
|
|
618
|
+
if (!isRetryable)
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
// Clean up temp file
|
|
622
|
+
try {
|
|
623
|
+
fs.unlinkSync(tempPath);
|
|
624
|
+
}
|
|
625
|
+
catch { /* ignore */ }
|
|
626
|
+
if (!uploadResult.success) {
|
|
627
|
+
throw new Error(`PDF upload failed: ${uploadResult.error}`);
|
|
628
|
+
}
|
|
629
|
+
results.push({
|
|
630
|
+
fileId: uploadResult.fileId || '',
|
|
631
|
+
sizeKB: uploadResult.sizeKB || '',
|
|
632
|
+
activityCount: batch.length,
|
|
633
|
+
batchNum: batchNum + 1
|
|
634
|
+
});
|
|
635
|
+
logger.debug(`Batch ${batchNum + 1} complete`, {
|
|
636
|
+
fileId: uploadResult.fileId,
|
|
637
|
+
sizeKB: uploadResult.sizeKB,
|
|
638
|
+
attempts: attempt + 1
|
|
639
|
+
});
|
|
640
|
+
success = true;
|
|
641
|
+
}
|
|
642
|
+
catch (batchError) {
|
|
643
|
+
lastError = batchError instanceof Error ? batchError.message : String(batchError);
|
|
644
|
+
// Check if error is retryable (502, 503, 504, 429)
|
|
645
|
+
const isRetryable = RETRY_CONFIG.retryableCodes.some(code => lastError.includes(String(code)));
|
|
646
|
+
if (!isRetryable) {
|
|
647
|
+
logger.debug(`Non-retryable error for batch ${batchNum + 1}: ${lastError}`);
|
|
648
|
+
break; // Don't retry non-retryable errors
|
|
649
|
+
}
|
|
650
|
+
logger.debug(`Retryable error for batch ${batchNum + 1} (attempt ${attempt + 1}): ${lastError}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (!success) {
|
|
654
|
+
errors.push({ batchNum: batchNum + 1, error: lastError });
|
|
655
|
+
logger.error(`Batch ${batchNum + 1} failed after ${RETRY_CONFIG.maxRetries} attempts`, { error: lastError });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Check if all batches failed
|
|
659
|
+
if (results.length === 0) {
|
|
660
|
+
throw new Error(`All ${batches.length} batches failed. First error: ${errors[0]?.error || 'Unknown'}`);
|
|
661
|
+
}
|
|
662
|
+
// Post to discussion if requested
|
|
663
|
+
if (args.discussionId && results.length > 0) {
|
|
664
|
+
const fileIds = results.map(r => r.fileId);
|
|
665
|
+
const message = batches.length > 1
|
|
666
|
+
? `š Generated ${results.length} PDFs from ${totalActivities} activities (${batches.length} batches)`
|
|
667
|
+
: `š Generated document from ${totalActivities} ${totalActivities === 1 ? 'activity' : 'activities'}`;
|
|
668
|
+
await context.hailer.sendDiscussionMessage(args.discussionId, message, fileIds);
|
|
669
|
+
logger.debug('Posted PDFs to discussion', {
|
|
670
|
+
discussionId: args.discussionId,
|
|
671
|
+
fileCount: fileIds.length
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
// Build response
|
|
675
|
+
const totalSizeKB = results.reduce((sum, r) => sum + parseFloat(r.sizeKB || '0'), 0).toFixed(2);
|
|
676
|
+
if (batches.length === 1) {
|
|
677
|
+
// Single batch response
|
|
678
|
+
const r = results[0];
|
|
679
|
+
let responseText = `ā
**Document generated successfully!**\n\n` +
|
|
680
|
+
`š **File Details:**\n` +
|
|
681
|
+
`- File ID: \`${r.fileId}\`\n` +
|
|
682
|
+
`- Size: ${r.sizeKB} KB\n` +
|
|
683
|
+
`- Activities: ${totalActivities}\n`;
|
|
684
|
+
if (args.discussionId) {
|
|
685
|
+
responseText += `\nš¬ **Posted to discussion**: ${args.discussionId}`;
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
responseText += `\nš” **Next Steps:**\n` +
|
|
689
|
+
`- Download: \`download_file({ fileId: "${r.fileId}" })\`\n` +
|
|
690
|
+
`- Post to discussion: \`add_discussion_message({ discussionId: "...", fileIds: ["${r.fileId}"] })\``;
|
|
691
|
+
}
|
|
692
|
+
return { content: [{ type: "text", text: responseText }] };
|
|
693
|
+
}
|
|
694
|
+
// Multi-batch response
|
|
695
|
+
let responseText = `ā
**Documents generated successfully!**\n\n` +
|
|
696
|
+
`š **Summary:**\n` +
|
|
697
|
+
`- Total activities: ${totalActivities}\n` +
|
|
698
|
+
`- Batches: ${batches.length} (${batchSize} per batch)\n` +
|
|
699
|
+
`- PDFs generated: ${results.length}\n` +
|
|
700
|
+
`- Total size: ${totalSizeKB} KB\n`;
|
|
701
|
+
if (errors.length > 0) {
|
|
702
|
+
responseText += `- Failed batches: ${errors.length}\n`;
|
|
703
|
+
}
|
|
704
|
+
responseText += `\nš **Generated Files:**\n`;
|
|
705
|
+
results.forEach(r => {
|
|
706
|
+
responseText += `- Batch ${r.batchNum}: \`${r.fileId}\` (${r.activityCount} activities, ${r.sizeKB} KB)\n`;
|
|
707
|
+
});
|
|
708
|
+
if (errors.length > 0) {
|
|
709
|
+
responseText += `\nā ļø **Failed Batches:**\n`;
|
|
710
|
+
errors.forEach(e => {
|
|
711
|
+
responseText += `- Batch ${e.batchNum}: ${e.error}\n`;
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (args.discussionId) {
|
|
715
|
+
responseText += `\nš¬ **Posted to discussion**: ${args.discussionId}`;
|
|
716
|
+
}
|
|
717
|
+
return { content: [{ type: "text", text: responseText }] };
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
if (!request_logger_1.RequestLogger.getCurrent())
|
|
721
|
+
logger.error("Failed to generate document", error);
|
|
722
|
+
const errorMessage = error instanceof Error
|
|
723
|
+
? error.message
|
|
724
|
+
: (typeof error === 'object' && error !== null)
|
|
725
|
+
? JSON.stringify(error, null, 2)
|
|
726
|
+
: String(error);
|
|
727
|
+
return {
|
|
728
|
+
content: [{
|
|
729
|
+
type: "text",
|
|
730
|
+
text: `ā Document generation failed: ${errorMessage}\n\n` +
|
|
731
|
+
`š” **Troubleshooting:**\n` +
|
|
732
|
+
`- Verify the template ID is correct (use list_document_templates)\n` +
|
|
733
|
+
`- Ensure all activity IDs are valid and accessible\n` +
|
|
734
|
+
`- For large batches, try reducing batchSize\n` +
|
|
735
|
+
`- Check that the template is compatible with the provided activities`
|
|
736
|
+
}]
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
//# sourceMappingURL=document.js.map
|