@nordbyte/nordrelay 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.env.example +80 -11
  2. package/README.md +154 -22
  3. package/dist/access-control.js +7 -1
  4. package/dist/activity-events.js +44 -0
  5. package/dist/audit-log.js +40 -2
  6. package/dist/bot-preferences.js +1 -0
  7. package/dist/bot-rendering.js +10 -7
  8. package/dist/bot.js +535 -11
  9. package/dist/channel-actions.js +7 -2
  10. package/dist/channel-adapter.js +40 -7
  11. package/dist/channel-command-catalog.js +88 -0
  12. package/dist/channel-command-service.js +369 -0
  13. package/dist/channel-mirror-registry.js +77 -0
  14. package/dist/channel-peer-prompt.js +95 -0
  15. package/dist/channel-runtime.js +12 -5
  16. package/dist/channel-turn-service.js +237 -0
  17. package/dist/codex-state.js +114 -78
  18. package/dist/config-metadata.js +93 -13
  19. package/dist/config.js +103 -8
  20. package/dist/context-key.js +87 -5
  21. package/dist/discord-artifacts.js +165 -0
  22. package/dist/discord-bot.js +2073 -0
  23. package/dist/discord-channel-runtime.js +133 -0
  24. package/dist/discord-command-surface.js +57 -0
  25. package/dist/discord-rate-limit.js +141 -0
  26. package/dist/index.js +36 -5
  27. package/dist/job-store.js +127 -0
  28. package/dist/metrics.js +87 -0
  29. package/dist/peer-auth.js +85 -0
  30. package/dist/peer-client.js +256 -0
  31. package/dist/peer-context.js +21 -0
  32. package/dist/peer-identity.js +127 -0
  33. package/dist/peer-runtime-service.js +636 -0
  34. package/dist/peer-server.js +220 -0
  35. package/dist/peer-store.js +294 -0
  36. package/dist/peer-types.js +52 -0
  37. package/dist/relay-external-activity-monitor.js +47 -6
  38. package/dist/relay-runtime-helpers.js +208 -0
  39. package/dist/relay-runtime.js +897 -394
  40. package/dist/remote-prompt.js +98 -0
  41. package/dist/runtime-cache.js +57 -0
  42. package/dist/session-locks.js +10 -7
  43. package/dist/support-bundle.js +1 -0
  44. package/dist/telegram-access-commands.js +15 -2
  45. package/dist/telegram-access-middleware.js +16 -3
  46. package/dist/telegram-agent-commands.js +25 -0
  47. package/dist/telegram-artifact-commands.js +46 -0
  48. package/dist/telegram-command-menu.js +3 -53
  49. package/dist/telegram-diagnostics-command.js +5 -50
  50. package/dist/telegram-general-commands.js +16 -6
  51. package/dist/telegram-operational-commands.js +14 -6
  52. package/dist/telegram-preference-commands.js +23 -127
  53. package/dist/telegram-queue-commands.js +74 -4
  54. package/dist/telegram-support-command.js +7 -0
  55. package/dist/telegram-update-commands.js +27 -0
  56. package/dist/user-management.js +208 -0
  57. package/dist/web-api-contract.js +17 -0
  58. package/dist/web-dashboard-access-routes.js +74 -1
  59. package/dist/web-dashboard-artifact-routes.js +3 -3
  60. package/dist/web-dashboard-assets.js +2 -0
  61. package/dist/web-dashboard-pages.js +109 -13
  62. package/dist/web-dashboard-peer-routes.js +204 -0
  63. package/dist/web-dashboard-runtime-routes.js +53 -8
  64. package/dist/web-dashboard-session-routes.js +27 -20
  65. package/dist/web-dashboard-ui.js +2 -0
  66. package/dist/web-dashboard.js +160 -6
  67. package/dist/web-state.js +33 -2
  68. package/dist/webui-assets/dashboard.css +75 -1
  69. package/dist/webui-assets/dashboard.js +779 -55
  70. package/package.json +5 -2
  71. package/plugins/nordrelay/scripts/nordrelay.mjs +578 -19
@@ -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 createDiscordArtifactCommandHandler(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, renderDiscordArtifactReports(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 deliverDiscordArtifactZip(deps, request, selected);
62
+ }
63
+ else {
64
+ await deliverDiscordArtifactReport(deps, request, selected);
65
+ }
66
+ return;
67
+ }
68
+ await deliverChannelAction(deps.runtime, request.context, renderDiscordArtifactReports(request.contextKey, reports));
69
+ };
70
+ }
71
+ export async function sendRecentDiscordArtifacts(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 sendDiscordArtifactFile(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 renderDiscordArtifactReports(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: `discord_artifact_send:${contextKey}:${report.turnId}` },
100
+ { label: `${index + 1} ZIP`, action: `discord_artifact_zip:${contextKey}:${report.turnId}` },
101
+ { label: `${index + 1} Delete`, action: `discord_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 deliverDiscordArtifactZip(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 Discord 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 deliverDiscordArtifactReport(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 sendDiscordArtifactFile(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 sendDiscordArtifactFile(deps, request, artifact) {
153
+ if (!deps.runtime.sendFile) {
154
+ await deps.reply(request, "This Discord 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 Discord artifact ${artifact.name}:`, error);
163
+ return false;
164
+ }
165
+ }