@exaudeus/workrail 3.31.1 → 3.33.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.
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-await.js +11 -9
- package/dist/cli/commands/worktrain-daemon-install.d.ts +35 -0
- package/dist/cli/commands/worktrain-daemon-install.js +291 -0
- package/dist/cli/commands/worktrain-daemon.d.ts +31 -0
- package/dist/cli/commands/worktrain-daemon.js +272 -0
- package/dist/cli/commands/worktrain-spawn.js +11 -9
- package/dist/cli-worktrain.js +329 -0
- package/dist/cli.js +4 -22
- package/dist/console/standalone-console.d.ts +28 -0
- package/dist/console/standalone-console.js +142 -0
- package/dist/{console/assets/index-6H9DeFxj.js → console-ui/assets/index-BuJFLLfY.js} +1 -1
- package/dist/{console → console-ui}/index.html +1 -1
- package/dist/daemon/agent-loop.d.ts +26 -0
- package/dist/daemon/agent-loop.js +53 -2
- package/dist/daemon/daemon-events.d.ts +103 -0
- package/dist/daemon/daemon-events.js +56 -0
- package/dist/daemon/workflow-runner.d.ts +6 -3
- package/dist/daemon/workflow-runner.js +229 -33
- package/dist/infrastructure/session/HttpServer.js +133 -34
- package/dist/manifest.json +134 -70
- package/dist/mcp/output-schemas.d.ts +30 -30
- package/dist/mcp/transports/bridge-events.d.ts +4 -0
- package/dist/mcp/transports/fatal-exit.js +4 -0
- package/dist/mcp/transports/http-entry.js +2 -0
- package/dist/mcp/transports/stdio-entry.js +26 -6
- package/dist/mcp/v2/tools.d.ts +4 -4
- package/dist/trigger/adapters/github-poller.d.ts +44 -0
- package/dist/trigger/adapters/github-poller.js +190 -0
- package/dist/trigger/adapters/gitlab-poller.d.ts +27 -0
- package/dist/trigger/adapters/gitlab-poller.js +81 -0
- package/dist/trigger/delivery-client.d.ts +2 -1
- package/dist/trigger/delivery-client.js +4 -1
- package/dist/trigger/index.d.ts +4 -1
- package/dist/trigger/index.js +5 -1
- package/dist/trigger/polled-event-store.d.ts +22 -0
- package/dist/trigger/polled-event-store.js +173 -0
- package/dist/trigger/polling-scheduler.d.ts +20 -0
- package/dist/trigger/polling-scheduler.js +249 -0
- package/dist/trigger/trigger-listener.d.ts +5 -0
- package/dist/trigger/trigger-listener.js +53 -4
- package/dist/trigger/trigger-router.d.ts +4 -2
- package/dist/trigger/trigger-router.js +7 -4
- package/dist/trigger/trigger-store.js +114 -33
- package/dist/trigger/types.d.ts +17 -1
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +224 -224
- package/dist/v2/durable-core/schemas/session/events.d.ts +42 -42
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +2 -2
- package/dist/v2/durable-core/tokens/payloads.d.ts +52 -52
- package/dist/v2/usecases/console-routes.js +3 -3
- package/dist/v2/usecases/console-service.js +133 -9
- package/dist/v2/usecases/console-types.d.ts +7 -0
- package/docs/design/daemon-conversation-logging-plan.md +98 -0
- package/docs/design/daemon-conversation-logging-review.md +55 -0
- package/docs/design/daemon-conversation-logging.md +129 -0
- package/docs/design/github-polling-adapter-design-candidates.md +226 -0
- package/docs/design/github-polling-adapter-design-review-findings.md +131 -0
- package/docs/design/github-polling-adapter-implementation-plan.md +284 -0
- package/docs/design/implementation_plan.md +192 -0
- package/docs/design/workflow-id-validation-at-startup.md +146 -0
- package/docs/design/workflow-id-validation-design-review.md +87 -0
- package/docs/design/workflow-id-validation-implementation-plan.md +185 -0
- package/docs/design/worktrain-system-prompt-report-issue-candidates.md +135 -0
- package/docs/design/worktrain-system-prompt-report-issue-design-review.md +73 -0
- package/docs/ideas/backlog.md +465 -0
- package/package.json +1 -1
- package/workflows/architecture-scalability-audit.json +1 -1
- package/workflows/bug-investigation.agentic.v2.json +3 -3
- package/workflows/coding-task-workflow-agentic.json +32 -32
- package/workflows/coding-task-workflow-agentic.lean.v2.json +1 -1
- package/workflows/coding-task-workflow-agentic.v2.json +7 -7
- package/workflows/mr-review-workflow.agentic.v2.json +21 -12
- package/workflows/personal-learning-materials-creation-branched.json +2 -2
- package/workflows/production-readiness-audit.json +1 -1
- package/workflows/relocation-workflow-us.json +2 -2
- package/workflows/ui-ux-design-workflow.json +14 -14
- package/workflows/workflow-for-workflows.json +3 -3
- package/workflows/workflow-for-workflows.v2.json +2 -2
- package/workflows/wr.discovery.json +1 -1
- /package/dist/{console → console-ui}/assets/index-8dh0Psu-.css +0 -0
|
@@ -38,6 +38,7 @@ const server_js_1 = require("../server.js");
|
|
|
38
38
|
const shutdown_hooks_js_1 = require("./shutdown-hooks.js");
|
|
39
39
|
const fatal_exit_js_1 = require("./fatal-exit.js");
|
|
40
40
|
const primary_tombstone_js_1 = require("./primary-tombstone.js");
|
|
41
|
+
const bridge_events_js_1 = require("./bridge-events.js");
|
|
41
42
|
const INITIAL_ROOTS_TIMEOUT_MS = 1000;
|
|
42
43
|
async function fetchInitialRootsWithTimeout(server) {
|
|
43
44
|
return Promise.race([
|
|
@@ -50,6 +51,7 @@ async function fetchInitialRootsWithTimeout(server) {
|
|
|
50
51
|
async function startStdioServer() {
|
|
51
52
|
(0, fatal_exit_js_1.registerFatalHandlers)('stdio');
|
|
52
53
|
(0, fatal_exit_js_1.logStartup)('stdio');
|
|
54
|
+
(0, bridge_events_js_1.logBridgeEvent)({ kind: 'primary_started', transport: 'stdio' });
|
|
53
55
|
(0, primary_tombstone_js_1.clearTombstone)();
|
|
54
56
|
const { server, ctx, rootsManager } = await (0, server_js_1.composeServer)();
|
|
55
57
|
(0, fatal_exit_js_1.registerGracefulShutdown)(async () => { await ctx.httpServer?.stop(); });
|
|
@@ -59,27 +61,45 @@ async function startStdioServer() {
|
|
|
59
61
|
try {
|
|
60
62
|
const result = await server.listRoots();
|
|
61
63
|
rootsManager.updateRootUris(result.roots.map((r) => r.uri));
|
|
62
|
-
|
|
64
|
+
try {
|
|
65
|
+
process.stderr.write(`[Roots] Updated workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}\n`);
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
63
68
|
}
|
|
64
69
|
catch {
|
|
65
|
-
|
|
70
|
+
try {
|
|
71
|
+
process.stderr.write('[Roots] Failed to fetch updated roots after change notification\n');
|
|
72
|
+
}
|
|
73
|
+
catch { }
|
|
66
74
|
}
|
|
67
75
|
});
|
|
68
76
|
(0, shutdown_hooks_js_1.wireStdoutShutdown)();
|
|
69
77
|
const transport = new StdioServerTransport();
|
|
70
78
|
await server.connect(transport);
|
|
71
|
-
|
|
79
|
+
try {
|
|
80
|
+
process.stderr.write('[Transport] WorkRail MCP Server running on stdio\n');
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
72
83
|
void fetchInitialRootsWithTimeout(server)
|
|
73
84
|
.then((result) => {
|
|
74
85
|
if (result == null) {
|
|
75
|
-
|
|
86
|
+
try {
|
|
87
|
+
process.stderr.write('[Roots] Initial roots probe timed out; workspace context will use server CWD fallback\n');
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
76
90
|
return;
|
|
77
91
|
}
|
|
78
92
|
rootsManager.updateRootUris(result.roots.map((r) => r.uri));
|
|
79
|
-
|
|
93
|
+
try {
|
|
94
|
+
process.stderr.write(`[Roots] Initial workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}\n`);
|
|
95
|
+
}
|
|
96
|
+
catch { }
|
|
80
97
|
})
|
|
81
98
|
.catch(() => {
|
|
82
|
-
|
|
99
|
+
try {
|
|
100
|
+
process.stderr.write('[Roots] Client does not support roots/list; workspace context will use server CWD fallback\n');
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
83
103
|
});
|
|
84
104
|
(0, shutdown_hooks_js_1.wireStdinShutdown)();
|
|
85
105
|
(0, shutdown_hooks_js_1.wireShutdownHooks)({
|
package/dist/mcp/v2/tools.d.ts
CHANGED
|
@@ -164,18 +164,18 @@ export declare const V2ResumeSessionInput: z.ZodObject<{
|
|
|
164
164
|
sameWorkspaceOnly: z.ZodOptional<z.ZodBoolean>;
|
|
165
165
|
}, "strict", z.ZodTypeAny, {
|
|
166
166
|
workspacePath: string;
|
|
167
|
-
query?: string | undefined;
|
|
168
|
-
runId?: string | undefined;
|
|
169
167
|
sessionId?: string | undefined;
|
|
168
|
+
runId?: string | undefined;
|
|
170
169
|
gitBranch?: string | undefined;
|
|
170
|
+
query?: string | undefined;
|
|
171
171
|
gitHeadSha?: string | undefined;
|
|
172
172
|
sameWorkspaceOnly?: boolean | undefined;
|
|
173
173
|
}, {
|
|
174
174
|
workspacePath: string;
|
|
175
|
-
query?: string | undefined;
|
|
176
|
-
runId?: string | undefined;
|
|
177
175
|
sessionId?: string | undefined;
|
|
176
|
+
runId?: string | undefined;
|
|
178
177
|
gitBranch?: string | undefined;
|
|
178
|
+
query?: string | undefined;
|
|
179
179
|
gitHeadSha?: string | undefined;
|
|
180
180
|
sameWorkspaceOnly?: boolean | undefined;
|
|
181
181
|
}>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GitHubPollingSource } from '../types.js';
|
|
2
|
+
import type { Result } from '../../runtime/result.js';
|
|
3
|
+
export interface GitHubLabel {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
}
|
|
6
|
+
export interface GitHubIssue {
|
|
7
|
+
readonly id: number;
|
|
8
|
+
readonly number: number;
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly html_url: string;
|
|
11
|
+
readonly updated_at: string;
|
|
12
|
+
readonly state: string;
|
|
13
|
+
readonly user?: {
|
|
14
|
+
readonly login?: string;
|
|
15
|
+
};
|
|
16
|
+
readonly labels?: readonly GitHubLabel[];
|
|
17
|
+
}
|
|
18
|
+
export interface GitHubPR {
|
|
19
|
+
readonly id: number;
|
|
20
|
+
readonly number: number;
|
|
21
|
+
readonly title: string;
|
|
22
|
+
readonly html_url: string;
|
|
23
|
+
readonly updated_at: string;
|
|
24
|
+
readonly state: string;
|
|
25
|
+
readonly user?: {
|
|
26
|
+
readonly login?: string;
|
|
27
|
+
};
|
|
28
|
+
readonly draft?: boolean;
|
|
29
|
+
readonly labels?: readonly GitHubLabel[];
|
|
30
|
+
}
|
|
31
|
+
export type GitHubPollError = {
|
|
32
|
+
readonly kind: 'http_error';
|
|
33
|
+
readonly status: number;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly kind: 'network_error';
|
|
37
|
+
readonly message: string;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: 'parse_error';
|
|
40
|
+
readonly message: string;
|
|
41
|
+
};
|
|
42
|
+
export type FetchFn = (url: string, init: RequestInit) => Promise<Response>;
|
|
43
|
+
export declare function pollGitHubIssues(source: GitHubPollingSource, since: string, fetchFn?: FetchFn): Promise<Result<GitHubIssue[], GitHubPollError>>;
|
|
44
|
+
export declare function pollGitHubPRs(source: GitHubPollingSource, since: string, fetchFn?: FetchFn): Promise<Result<GitHubPR[], GitHubPollError>>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pollGitHubIssues = pollGitHubIssues;
|
|
4
|
+
exports.pollGitHubPRs = pollGitHubPRs;
|
|
5
|
+
const result_js_1 = require("../../runtime/result.js");
|
|
6
|
+
function checkRateLimit(response) {
|
|
7
|
+
const remainingHeader = response.headers.get('X-RateLimit-Remaining');
|
|
8
|
+
const resetHeader = response.headers.get('X-RateLimit-Reset');
|
|
9
|
+
if (remainingHeader === null)
|
|
10
|
+
return true;
|
|
11
|
+
const remaining = parseInt(remainingHeader, 10);
|
|
12
|
+
if (isNaN(remaining) || remaining >= 100)
|
|
13
|
+
return true;
|
|
14
|
+
const resetTs = parseInt(resetHeader ?? '0', 10);
|
|
15
|
+
const resetAt = resetTs > 0 ? new Date(resetTs * 1000).toISOString() : 'unknown';
|
|
16
|
+
console.warn(`[GitHubPoller] Rate limit low: remaining=${remaining}, resets at ${resetAt}. ` +
|
|
17
|
+
`Skipping poll cycle to avoid exhaustion.`);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
async function pollGitHubIssues(source, since, fetchFn = globalThis.fetch) {
|
|
21
|
+
const [owner, repo] = source.repo.split('/');
|
|
22
|
+
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/issues`);
|
|
23
|
+
url.searchParams.set('state', 'open');
|
|
24
|
+
url.searchParams.set('since', since);
|
|
25
|
+
url.searchParams.set('sort', 'updated');
|
|
26
|
+
url.searchParams.set('direction', 'desc');
|
|
27
|
+
url.searchParams.set('per_page', '100');
|
|
28
|
+
if (source.labelFilter.length > 0) {
|
|
29
|
+
url.searchParams.set('labels', source.labelFilter.join(','));
|
|
30
|
+
}
|
|
31
|
+
let response;
|
|
32
|
+
try {
|
|
33
|
+
response = await fetchFn(url.toString(), {
|
|
34
|
+
headers: {
|
|
35
|
+
'Authorization': `Bearer ${source.token}`,
|
|
36
|
+
'Accept': 'application/vnd.github+json',
|
|
37
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return (0, result_js_1.err)({
|
|
43
|
+
kind: 'network_error',
|
|
44
|
+
message: e instanceof Error ? e.message : String(e),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
return (0, result_js_1.err)({
|
|
49
|
+
kind: 'http_error',
|
|
50
|
+
status: response.status,
|
|
51
|
+
message: `GitHub API returned HTTP ${response.status}: ${response.statusText}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (!checkRateLimit(response)) {
|
|
55
|
+
return (0, result_js_1.ok)([]);
|
|
56
|
+
}
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = await response.json();
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return (0, result_js_1.err)({
|
|
63
|
+
kind: 'parse_error',
|
|
64
|
+
message: `Failed to parse GitHub Issues API response: ${e instanceof Error ? e.message : String(e)}`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(raw)) {
|
|
68
|
+
return (0, result_js_1.err)({
|
|
69
|
+
kind: 'parse_error',
|
|
70
|
+
message: `Expected array from GitHub Issues API, got: ${typeof raw}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const issues = [];
|
|
74
|
+
for (const item of raw) {
|
|
75
|
+
if (isGitHubIssueShape(item)) {
|
|
76
|
+
issues.push(item);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return (0, result_js_1.ok)(applyIssueFilters(issues, source));
|
|
80
|
+
}
|
|
81
|
+
async function pollGitHubPRs(source, since, fetchFn = globalThis.fetch) {
|
|
82
|
+
const [owner, repo] = source.repo.split('/');
|
|
83
|
+
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/pulls`);
|
|
84
|
+
url.searchParams.set('state', 'open');
|
|
85
|
+
url.searchParams.set('sort', 'updated');
|
|
86
|
+
url.searchParams.set('direction', 'desc');
|
|
87
|
+
url.searchParams.set('per_page', '100');
|
|
88
|
+
let response;
|
|
89
|
+
try {
|
|
90
|
+
response = await fetchFn(url.toString(), {
|
|
91
|
+
headers: {
|
|
92
|
+
'Authorization': `Bearer ${source.token}`,
|
|
93
|
+
'Accept': 'application/vnd.github+json',
|
|
94
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
return (0, result_js_1.err)({
|
|
100
|
+
kind: 'network_error',
|
|
101
|
+
message: e instanceof Error ? e.message : String(e),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
return (0, result_js_1.err)({
|
|
106
|
+
kind: 'http_error',
|
|
107
|
+
status: response.status,
|
|
108
|
+
message: `GitHub API returned HTTP ${response.status}: ${response.statusText}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (!checkRateLimit(response)) {
|
|
112
|
+
return (0, result_js_1.ok)([]);
|
|
113
|
+
}
|
|
114
|
+
let raw;
|
|
115
|
+
try {
|
|
116
|
+
raw = await response.json();
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
return (0, result_js_1.err)({
|
|
120
|
+
kind: 'parse_error',
|
|
121
|
+
message: `Failed to parse GitHub PRs API response: ${e instanceof Error ? e.message : String(e)}`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(raw)) {
|
|
125
|
+
return (0, result_js_1.err)({
|
|
126
|
+
kind: 'parse_error',
|
|
127
|
+
message: `Expected array from GitHub PRs API, got: ${typeof raw}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const prs = [];
|
|
131
|
+
for (const item of raw) {
|
|
132
|
+
if (isGitHubPRShape(item)) {
|
|
133
|
+
prs.push(item);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const filtered = prs.filter(pr => pr.updated_at > since);
|
|
137
|
+
return (0, result_js_1.ok)(applyPRFilters(filtered, source));
|
|
138
|
+
}
|
|
139
|
+
function applyIssueFilters(issues, source) {
|
|
140
|
+
return issues.filter(issue => {
|
|
141
|
+
if (source.excludeAuthors.length > 0) {
|
|
142
|
+
const login = issue.user?.login;
|
|
143
|
+
if (!login || source.excludeAuthors.includes(login))
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (source.notLabels.length > 0 && issue.labels) {
|
|
147
|
+
const labelNames = issue.labels.map(l => l.name);
|
|
148
|
+
if (source.notLabels.some(nl => labelNames.includes(nl)))
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function applyPRFilters(prs, source) {
|
|
155
|
+
return prs.filter(pr => {
|
|
156
|
+
if (source.excludeAuthors.length > 0) {
|
|
157
|
+
const login = pr.user?.login;
|
|
158
|
+
if (!login || source.excludeAuthors.includes(login))
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (source.notLabels.length > 0 && pr.labels) {
|
|
162
|
+
const labelNames = pr.labels.map(l => l.name);
|
|
163
|
+
if (source.notLabels.some(nl => labelNames.includes(nl)))
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function isGitHubIssueShape(item) {
|
|
170
|
+
if (typeof item !== 'object' || item === null)
|
|
171
|
+
return false;
|
|
172
|
+
const obj = item;
|
|
173
|
+
return (typeof obj['id'] === 'number' &&
|
|
174
|
+
typeof obj['number'] === 'number' &&
|
|
175
|
+
typeof obj['title'] === 'string' &&
|
|
176
|
+
typeof obj['html_url'] === 'string' &&
|
|
177
|
+
typeof obj['updated_at'] === 'string' &&
|
|
178
|
+
typeof obj['state'] === 'string');
|
|
179
|
+
}
|
|
180
|
+
function isGitHubPRShape(item) {
|
|
181
|
+
if (typeof item !== 'object' || item === null)
|
|
182
|
+
return false;
|
|
183
|
+
const obj = item;
|
|
184
|
+
return (typeof obj['id'] === 'number' &&
|
|
185
|
+
typeof obj['number'] === 'number' &&
|
|
186
|
+
typeof obj['title'] === 'string' &&
|
|
187
|
+
typeof obj['html_url'] === 'string' &&
|
|
188
|
+
typeof obj['updated_at'] === 'string' &&
|
|
189
|
+
typeof obj['state'] === 'string');
|
|
190
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GitLabPollingSource } from '../types.js';
|
|
2
|
+
import type { Result } from '../../runtime/result.js';
|
|
3
|
+
export interface GitLabMR {
|
|
4
|
+
readonly id: number;
|
|
5
|
+
readonly iid: number;
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly web_url: string;
|
|
8
|
+
readonly updated_at: string;
|
|
9
|
+
readonly state: string;
|
|
10
|
+
readonly author?: {
|
|
11
|
+
readonly username?: string;
|
|
12
|
+
readonly name?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export type GitLabPollError = {
|
|
16
|
+
readonly kind: 'http_error';
|
|
17
|
+
readonly status: number;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: 'network_error';
|
|
21
|
+
readonly message: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'parse_error';
|
|
24
|
+
readonly message: string;
|
|
25
|
+
};
|
|
26
|
+
export type FetchFn = (url: string, init: RequestInit) => Promise<Response>;
|
|
27
|
+
export declare function pollGitLabMRs(source: GitLabPollingSource, updatedAfter: string, fetchFn?: FetchFn): Promise<Result<GitLabMR[], GitLabPollError>>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pollGitLabMRs = pollGitLabMRs;
|
|
4
|
+
const result_js_1 = require("../../runtime/result.js");
|
|
5
|
+
async function pollGitLabMRs(source, updatedAfter, fetchFn = globalThis.fetch) {
|
|
6
|
+
const encodedProjectId = encodeURIComponent(source.projectId);
|
|
7
|
+
const baseUrl = source.baseUrl.replace(/\/$/, '');
|
|
8
|
+
const url = new URL(`${baseUrl}/api/v4/projects/${encodedProjectId}/merge_requests`);
|
|
9
|
+
url.searchParams.set('state', 'opened');
|
|
10
|
+
url.searchParams.set('updated_after', updatedAfter);
|
|
11
|
+
url.searchParams.set('per_page', '100');
|
|
12
|
+
let response;
|
|
13
|
+
try {
|
|
14
|
+
response = await fetchFn(url.toString(), {
|
|
15
|
+
headers: {
|
|
16
|
+
'PRIVATE-TOKEN': source.token,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return (0, result_js_1.err)({
|
|
23
|
+
kind: 'network_error',
|
|
24
|
+
message: e instanceof Error ? e.message : String(e),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
return (0, result_js_1.err)({
|
|
29
|
+
kind: 'http_error',
|
|
30
|
+
status: response.status,
|
|
31
|
+
message: `GitLab API returned HTTP ${response.status}: ${response.statusText}`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
let raw;
|
|
35
|
+
try {
|
|
36
|
+
raw = await response.json();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return (0, result_js_1.err)({
|
|
40
|
+
kind: 'parse_error',
|
|
41
|
+
message: `Failed to parse GitLab API response: ${e instanceof Error ? e.message : String(e)}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (!Array.isArray(raw)) {
|
|
45
|
+
return (0, result_js_1.err)({
|
|
46
|
+
kind: 'parse_error',
|
|
47
|
+
message: `Expected array from GitLab MR API, got: ${typeof raw}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const mrs = [];
|
|
51
|
+
for (const item of raw) {
|
|
52
|
+
if (isGitLabMRShape(item)) {
|
|
53
|
+
mrs.push(item);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const filtered = applyEventFilter(mrs, source.events);
|
|
57
|
+
return (0, result_js_1.ok)(filtered);
|
|
58
|
+
}
|
|
59
|
+
function applyEventFilter(mrs, events) {
|
|
60
|
+
if (events.length === 0)
|
|
61
|
+
return mrs;
|
|
62
|
+
const includeOpened = events.includes('merge_request.opened');
|
|
63
|
+
const includeUpdated = events.includes('merge_request.updated');
|
|
64
|
+
if (!includeOpened && !includeUpdated) {
|
|
65
|
+
return mrs;
|
|
66
|
+
}
|
|
67
|
+
if (includeUpdated)
|
|
68
|
+
return mrs;
|
|
69
|
+
return mrs.filter(mr => mr.state === 'opened');
|
|
70
|
+
}
|
|
71
|
+
function isGitLabMRShape(item) {
|
|
72
|
+
if (typeof item !== 'object' || item === null)
|
|
73
|
+
return false;
|
|
74
|
+
const obj = item;
|
|
75
|
+
return (typeof obj['id'] === 'number' &&
|
|
76
|
+
typeof obj['iid'] === 'number' &&
|
|
77
|
+
typeof obj['title'] === 'string' &&
|
|
78
|
+
typeof obj['web_url'] === 'string' &&
|
|
79
|
+
typeof obj['updated_at'] === 'string' &&
|
|
80
|
+
typeof obj['state'] === 'string');
|
|
81
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Result } from '../runtime/result.js';
|
|
2
2
|
import type { WorkflowRunResult } from '../daemon/workflow-runner.js';
|
|
3
|
+
import type { DaemonEventEmitter } from '../daemon/daemon-events.js';
|
|
3
4
|
export type DeliveryError = {
|
|
4
5
|
readonly kind: 'http_error';
|
|
5
6
|
readonly status: number;
|
|
@@ -8,4 +9,4 @@ export type DeliveryError = {
|
|
|
8
9
|
readonly kind: 'network_error';
|
|
9
10
|
readonly message: string;
|
|
10
11
|
};
|
|
11
|
-
export declare function post(callbackUrl: string, result: WorkflowRunResult): Promise<Result<void, DeliveryError>>;
|
|
12
|
+
export declare function post(callbackUrl: string, result: WorkflowRunResult, emitter?: DaemonEventEmitter): Promise<Result<void, DeliveryError>>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.post = post;
|
|
4
4
|
const result_js_1 = require("../runtime/result.js");
|
|
5
|
-
async function post(callbackUrl, result) {
|
|
5
|
+
async function post(callbackUrl, result, emitter) {
|
|
6
6
|
const controller = new AbortController();
|
|
7
7
|
const timer = setTimeout(() => controller.abort(), 30000);
|
|
8
8
|
try {
|
|
@@ -14,11 +14,14 @@ async function post(callbackUrl, result) {
|
|
|
14
14
|
});
|
|
15
15
|
if (!res.ok) {
|
|
16
16
|
const body = await res.text().catch(() => '');
|
|
17
|
+
emitter?.emit({ kind: 'delivery_attempted', callbackUrl, outcome: 'http_error', statusCode: res.status });
|
|
17
18
|
return (0, result_js_1.err)({ kind: 'http_error', status: res.status, body });
|
|
18
19
|
}
|
|
20
|
+
emitter?.emit({ kind: 'delivery_attempted', callbackUrl, outcome: 'success', statusCode: res.status });
|
|
19
21
|
return (0, result_js_1.ok)(undefined);
|
|
20
22
|
}
|
|
21
23
|
catch (e) {
|
|
24
|
+
emitter?.emit({ kind: 'delivery_attempted', callbackUrl, outcome: 'network_error' });
|
|
22
25
|
return (0, result_js_1.err)({ kind: 'network_error', message: String(e) });
|
|
23
26
|
}
|
|
24
27
|
finally {
|
package/dist/trigger/index.d.ts
CHANGED
|
@@ -2,4 +2,7 @@ export { startTriggerListener } from './trigger-listener.js';
|
|
|
2
2
|
export type { TriggerListenerHandle, TriggerListenerError, StartTriggerListenerOptions } from './trigger-listener.js';
|
|
3
3
|
export { loadTriggerConfig, loadTriggerConfigFromFile } from './trigger-store.js';
|
|
4
4
|
export type { TriggerStoreError } from './trigger-store.js';
|
|
5
|
-
export type { TriggerId, TriggerDefinition, TriggerConfig, TriggerSource, WebhookEvent, ContextMapping, ContextMappingEntry, } from './types.js';
|
|
5
|
+
export type { TriggerId, TriggerDefinition, TriggerConfig, TriggerSource, WebhookEvent, ContextMapping, ContextMappingEntry, GitLabPollingSource, GitHubPollingSource, PollingSource, } from './types.js';
|
|
6
|
+
export { PolledEventStore } from './polled-event-store.js';
|
|
7
|
+
export type { PolledEventState, PolledEventStoreError } from './polled-event-store.js';
|
|
8
|
+
export { PollingScheduler } from './polling-scheduler.js';
|
package/dist/trigger/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadTriggerConfigFromFile = exports.loadTriggerConfig = exports.startTriggerListener = void 0;
|
|
3
|
+
exports.PollingScheduler = exports.PolledEventStore = exports.loadTriggerConfigFromFile = exports.loadTriggerConfig = exports.startTriggerListener = void 0;
|
|
4
4
|
var trigger_listener_js_1 = require("./trigger-listener.js");
|
|
5
5
|
Object.defineProperty(exports, "startTriggerListener", { enumerable: true, get: function () { return trigger_listener_js_1.startTriggerListener; } });
|
|
6
6
|
var trigger_store_js_1 = require("./trigger-store.js");
|
|
7
7
|
Object.defineProperty(exports, "loadTriggerConfig", { enumerable: true, get: function () { return trigger_store_js_1.loadTriggerConfig; } });
|
|
8
8
|
Object.defineProperty(exports, "loadTriggerConfigFromFile", { enumerable: true, get: function () { return trigger_store_js_1.loadTriggerConfigFromFile; } });
|
|
9
|
+
var polled_event_store_js_1 = require("./polled-event-store.js");
|
|
10
|
+
Object.defineProperty(exports, "PolledEventStore", { enumerable: true, get: function () { return polled_event_store_js_1.PolledEventStore; } });
|
|
11
|
+
var polling_scheduler_js_1 = require("./polling-scheduler.js");
|
|
12
|
+
Object.defineProperty(exports, "PollingScheduler", { enumerable: true, get: function () { return polling_scheduler_js_1.PollingScheduler; } });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Result } from '../runtime/result.js';
|
|
2
|
+
import type { TriggerId } from './types.js';
|
|
3
|
+
export interface PolledEventState {
|
|
4
|
+
readonly processedIds: readonly string[];
|
|
5
|
+
readonly lastPollAt: string;
|
|
6
|
+
}
|
|
7
|
+
export type PolledEventStoreError = {
|
|
8
|
+
readonly kind: 'io_error';
|
|
9
|
+
readonly message: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'write_error';
|
|
12
|
+
readonly message: string;
|
|
13
|
+
};
|
|
14
|
+
export declare class PolledEventStore {
|
|
15
|
+
private readonly env;
|
|
16
|
+
constructor(env?: Record<string, string | undefined>);
|
|
17
|
+
load(triggerId: TriggerId): Promise<Result<PolledEventState, PolledEventStoreError>>;
|
|
18
|
+
save(triggerId: TriggerId, state: PolledEventState): Promise<Result<void, PolledEventStoreError>>;
|
|
19
|
+
filterNew(triggerId: TriggerId, candidateIds: readonly string[]): Promise<Result<string[], PolledEventStoreError>>;
|
|
20
|
+
record(triggerId: TriggerId, newIds: readonly string[], lastPollAt: string): Promise<Result<void, PolledEventStoreError>>;
|
|
21
|
+
getLastPollAt(triggerId: TriggerId): Promise<string>;
|
|
22
|
+
}
|