@nordbyte/nordrelay 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.env.example +35 -0
  2. package/README.md +118 -49
  3. package/dist/activity-events.js +2 -2
  4. package/dist/adapter-conformance.js +61 -0
  5. package/dist/bot.js +18 -31
  6. package/dist/channel-adapter.js +33 -6
  7. package/dist/channel-command-catalog.js +6 -0
  8. package/dist/channel-command-core.js +60 -0
  9. package/dist/channel-command-service.js +20 -4
  10. package/dist/channel-mirror-registry.js +9 -2
  11. package/dist/channel-prompt-engine.js +177 -0
  12. package/dist/channel-turn-lifecycle.js +73 -0
  13. package/dist/config-metadata.js +67 -8
  14. package/dist/config.js +48 -1
  15. package/dist/context-key.js +32 -0
  16. package/dist/discord-bot.js +99 -327
  17. package/dist/index.js +9 -0
  18. package/dist/metrics.js +2 -0
  19. package/dist/peer-client.js +90 -2
  20. package/dist/peer-readiness.js +77 -0
  21. package/dist/peer-runtime-service.js +22 -0
  22. package/dist/peer-server.js +20 -4
  23. package/dist/peer-store.js +17 -2
  24. package/dist/relay-runtime-helpers.js +3 -1
  25. package/dist/relay-runtime.js +7 -0
  26. package/dist/settings-wizard-test.js +216 -0
  27. package/dist/slack-artifacts.js +165 -0
  28. package/dist/slack-bot.js +1461 -0
  29. package/dist/slack-channel-runtime.js +147 -0
  30. package/dist/slack-command-surface.js +46 -0
  31. package/dist/slack-diagnostics.js +116 -0
  32. package/dist/slack-rate-limit.js +139 -0
  33. package/dist/user-management-crypto.js +38 -0
  34. package/dist/user-management-normalize.js +188 -0
  35. package/dist/user-management-types.js +1 -0
  36. package/dist/user-management.js +193 -196
  37. package/dist/web-api-contract.js +8 -0
  38. package/dist/web-dashboard-access-routes.js +62 -0
  39. package/dist/web-dashboard-assets.js +1 -0
  40. package/dist/web-dashboard-pages.js +14 -4
  41. package/dist/web-dashboard-peer-routes.js +32 -11
  42. package/dist/web-dashboard.js +34 -0
  43. package/dist/web-state.js +2 -2
  44. package/dist/webui-assets/dashboard.css +193 -0
  45. package/dist/webui-assets/dashboard.js +546 -145
  46. package/package.json +3 -1
  47. package/plugins/nordrelay/scripts/nordrelay.mjs +105 -11
@@ -0,0 +1,165 @@
1
+ import { collectRecentWorkspaceArtifacts, createArtifactZipBundle, formatArtifactSummary, listRecentArtifactReports } from "./artifacts.js";
2
+ import { filterArtifactReports as filterArtifactReportsForCommand } from "./bot-rendering.js";
3
+ import { renderArtifactReportsAction } from "./channel-actions.js";
4
+ import { deliverChannelAction } from "./channel-runtime.js";
5
+ export function createSlackArtifactCommandHandler(deps) {
6
+ return async (request, argument) => {
7
+ const session = await deps.getSession(request, { deferThreadStart: true });
8
+ const [action, turnId] = argument.trim().split(/\s+/, 2);
9
+ const info = session.getInfo();
10
+ const workspace = info.workspace;
11
+ const reports = await listRecentArtifactReports(workspace, 10, deps.config.maxFileSize);
12
+ if (reports.length === 0) {
13
+ await deps.reply(request, "No generated artifacts found for this workspace.");
14
+ return;
15
+ }
16
+ if (action) {
17
+ if (action.toLowerCase() === "delete" && turnId) {
18
+ const selected = findArtifactReport(reports, turnId);
19
+ if (!selected) {
20
+ await deps.reply(request, `No artifact turn found for "${turnId}".`);
21
+ return;
22
+ }
23
+ const removed = await deps.artifactService.delete(workspace, selected.turnId);
24
+ deps.appendActivity(request, {
25
+ status: removed ? "info" : "failed",
26
+ type: "artifact_deleted",
27
+ threadId: info.threadId,
28
+ workspace,
29
+ agentId: info.agentId,
30
+ detail: selected.turnId,
31
+ });
32
+ await deps.reply(request, removed ? `Deleted artifact turn: ${selected.turnId}` : `Artifact turn not found: ${selected.turnId}`);
33
+ return;
34
+ }
35
+ const filtered = filterArtifactReportsForCommand(reports, argument);
36
+ if (filtered) {
37
+ if (filtered.length === 0) {
38
+ await deps.reply(request, `No artifacts matched "${argument}".`);
39
+ return;
40
+ }
41
+ await deliverChannelAction(deps.runtime, request.context, renderSlackArtifactReports(request.contextKey, filtered));
42
+ return;
43
+ }
44
+ const normalizedAction = action.toLowerCase();
45
+ const shouldZip = normalizedAction === "zip";
46
+ const shouldSend = normalizedAction === "send";
47
+ const selected = findArtifactReport(reports, shouldZip || shouldSend ? turnId : action);
48
+ if (!selected) {
49
+ await deps.reply(request, `No artifact turn found for "${argument}".`);
50
+ return;
51
+ }
52
+ deps.appendActivity(request, {
53
+ status: "info",
54
+ type: shouldZip ? "artifact_zip_sent" : "artifacts_sent",
55
+ threadId: info.threadId,
56
+ workspace,
57
+ agentId: info.agentId,
58
+ detail: selected.turnId,
59
+ });
60
+ if (shouldZip) {
61
+ await deliverSlackArtifactZip(deps, request, selected);
62
+ }
63
+ else {
64
+ await deliverSlackArtifactReport(deps, request, selected);
65
+ }
66
+ return;
67
+ }
68
+ await deliverChannelAction(deps.runtime, request.context, renderSlackArtifactReports(request.contextKey, reports));
69
+ };
70
+ }
71
+ export async function sendRecentSlackArtifacts(deps, request, session, since, turnId) {
72
+ const report = await collectRecentWorkspaceArtifacts(session.getInfo().workspace, {
73
+ since,
74
+ until: new Date(),
75
+ maxFileSize: deps.config.maxFileSize,
76
+ limit: 5,
77
+ });
78
+ if (report.artifacts.length === 0) {
79
+ return;
80
+ }
81
+ await deps.reply(request, `${report.artifacts.length} artifacts generated.`);
82
+ for (const artifact of report.artifacts.slice(0, 5)) {
83
+ await sendSlackArtifactFile(deps, request, artifact);
84
+ }
85
+ deps.appendActivity(request, {
86
+ status: "info",
87
+ type: "artifacts_sent",
88
+ detail: `${report.artifacts.length} artifacts for ${turnId}`,
89
+ threadId: session.getInfo().threadId,
90
+ workspace: session.getInfo().workspace,
91
+ agentId: session.getInfo().agentId,
92
+ });
93
+ }
94
+ function renderSlackArtifactReports(contextKey, reports) {
95
+ const rendered = renderArtifactReportsAction(reports);
96
+ return {
97
+ ...rendered,
98
+ buttons: reports.slice(0, 5).map((report, index) => [
99
+ { label: `${index + 1} Send`, action: `slack_artifact_send:${contextKey}:${report.turnId}` },
100
+ { label: `${index + 1} ZIP`, action: `slack_artifact_zip:${contextKey}:${report.turnId}` },
101
+ { label: `${index + 1} Delete`, action: `slack_artifact_delete:${contextKey}:${report.turnId}` },
102
+ ]),
103
+ };
104
+ }
105
+ function findArtifactReport(reports, requested) {
106
+ const value = requested?.trim();
107
+ if (!value || value.toLowerCase() === "latest") {
108
+ return reports[0];
109
+ }
110
+ return reports.find((report) => report.turnId === value || report.turnId.startsWith(value));
111
+ }
112
+ async function deliverSlackArtifactZip(deps, request, report) {
113
+ const bundle = await createArtifactZipBundle(report.artifacts, report.outDir, {
114
+ maxFileSize: deps.config.maxFileSize,
115
+ bundleName: `nordrelay-artifacts-${report.turnId}.zip`,
116
+ });
117
+ if (!bundle) {
118
+ await deps.reply(request, "Could not create a ZIP bundle for this artifact turn.");
119
+ return;
120
+ }
121
+ if (!deps.runtime.sendFile) {
122
+ await deps.reply(request, "This Slack runtime cannot send artifact files.");
123
+ return;
124
+ }
125
+ await deps.runtime.sendFile(request.context, { localPath: bundle.localPath, name: bundle.name });
126
+ await deps.reply(request, `Sent ZIP artifact bundle: ${bundle.name}`);
127
+ }
128
+ async function deliverSlackArtifactReport(deps, request, report) {
129
+ if (report.artifacts.length === 0 && report.skippedCount === 0 && !report.omittedCount) {
130
+ await deps.reply(request, "No generated artifacts found for this turn.");
131
+ return;
132
+ }
133
+ let failedCount = 0;
134
+ let bundledArtifact = null;
135
+ if (report.artifacts.length > 5) {
136
+ bundledArtifact = await createArtifactZipBundle(report.artifacts, report.outDir, {
137
+ maxFileSize: deps.config.maxFileSize,
138
+ });
139
+ }
140
+ const delivered = bundledArtifact ? [bundledArtifact] : report.artifacts;
141
+ for (const artifact of delivered) {
142
+ if (!await sendSlackArtifactFile(deps, request, artifact)) {
143
+ failedCount += 1;
144
+ }
145
+ }
146
+ const summary = formatArtifactSummary(report.artifacts, report.skippedCount + failedCount, report.omittedCount);
147
+ if (summary) {
148
+ const bundleNote = bundledArtifact ? `\nSent as ZIP: ${bundledArtifact.name}` : "";
149
+ await deps.reply(request, `${summary}${bundleNote}`);
150
+ }
151
+ }
152
+ async function sendSlackArtifactFile(deps, request, artifact) {
153
+ if (!deps.runtime.sendFile) {
154
+ await deps.reply(request, "This Slack runtime cannot send artifact files.");
155
+ return false;
156
+ }
157
+ try {
158
+ await deps.runtime.sendFile(request.context, { localPath: artifact.localPath, name: artifact.name });
159
+ return true;
160
+ }
161
+ catch (error) {
162
+ console.error(`Failed to send Slack artifact ${artifact.name}:`, error);
163
+ return false;
164
+ }
165
+ }