@getpaseo/server 0.1.93 → 0.1.94
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/server/server/agent/agent-manager.d.ts +14 -0
- package/dist/server/server/agent/agent-manager.js +36 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/agent.js +58 -3
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
- package/dist/server/server/agent/providers/mock-load-test-agent.js +55 -28
- package/dist/server/server/agent/providers/pi/agent.js +3 -1
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +5 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +74 -12
- package/dist/server/server/agent/runtime-mcp-config.d.ts +6 -0
- package/dist/server/server/agent/runtime-mcp-config.js +3 -0
- package/dist/server/server/auth.d.ts +13 -0
- package/dist/server/server/auth.js +35 -0
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +28 -1
- package/dist/server/server/config.js +3 -1
- package/dist/server/server/daemon-config-store.js +3 -0
- package/dist/server/server/loop-service.d.ts +6 -6
- package/dist/server/server/persisted-config.d.ts +61 -0
- package/dist/server/server/persisted-config.js +2 -0
- package/dist/server/server/session.d.ts +1 -0
- package/dist/server/server/session.js +50 -3
- package/dist/server/server/websocket-server.js +2 -0
- package/dist/server/server/workspace-git-service.d.ts +4 -1
- package/dist/server/server/workspace-git-service.js +40 -22
- package/dist/server/services/github-service.d.ts +57 -0
- package/dist/server/services/github-service.js +327 -3
- package/dist/server/terminal/terminal-session-controller.d.ts +6 -0
- package/dist/server/terminal/terminal-session-controller.js +36 -2
- package/dist/src/server/persisted-config.js +2 -0
- package/package.json +5 -5
|
@@ -4,6 +4,12 @@ import { resolveGitHubRemote } from "../utils/github-remote.js";
|
|
|
4
4
|
import { runGitCommand } from "../utils/run-git-command.js";
|
|
5
5
|
import { execCommand } from "../utils/spawn.js";
|
|
6
6
|
const DEFAULT_GITHUB_CACHE_TTL_MS = 30000;
|
|
7
|
+
const CHECK_ANNOTATION_PAGE_MAX = 20;
|
|
8
|
+
const CHECK_LOG_TAIL_MAX_LINES = 200;
|
|
9
|
+
const CHECK_LOG_TAIL_MAX_BYTES = 16 * 1024;
|
|
10
|
+
const CHECK_LOG_TAIL_CACHE_MAX_ENTRIES = 128;
|
|
11
|
+
const ACTIONS_JOB_PAGE_MAX = 100;
|
|
12
|
+
const FAILED_CHECK_JOB_LIMIT = 5;
|
|
7
13
|
export const GITHUB_POLL_FAST_INTERVAL_MS = 20000;
|
|
8
14
|
export const GITHUB_POLL_SLOW_INTERVAL_MS = 120000;
|
|
9
15
|
export const GITHUB_POLL_ERROR_BACKOFF_CAP_MS = 300000;
|
|
@@ -35,6 +41,7 @@ const GitHubPullRequestSummarySchema = z.object({
|
|
|
35
41
|
});
|
|
36
42
|
const PullRequestCheckRunNodeSchema = z.object({
|
|
37
43
|
__typename: z.literal("CheckRun"),
|
|
44
|
+
databaseId: z.number().nullable().optional(),
|
|
38
45
|
name: z.string(),
|
|
39
46
|
workflowName: z.string().nullable().optional(),
|
|
40
47
|
conclusion: z.string().nullable().optional(),
|
|
@@ -69,6 +76,54 @@ const PullRequestStatusCheckRollupArraySchema = z.array(z.unknown());
|
|
|
69
76
|
const LegacyPullRequestStatusCheckRollupSchema = z.object({
|
|
70
77
|
contexts: z.array(z.unknown()),
|
|
71
78
|
});
|
|
79
|
+
const GitHubCheckRunDetailsSchema = z.object({
|
|
80
|
+
id: z.number(),
|
|
81
|
+
name: z.string().catch(""),
|
|
82
|
+
status: z.string().nullable().optional(),
|
|
83
|
+
conclusion: z.string().nullable().optional(),
|
|
84
|
+
html_url: z.string().nullable().optional(),
|
|
85
|
+
details_url: z.string().nullable().optional(),
|
|
86
|
+
output: z
|
|
87
|
+
.object({
|
|
88
|
+
title: z.string().nullable().optional(),
|
|
89
|
+
summary: z.string().nullable().optional(),
|
|
90
|
+
text: z.string().nullable().optional(),
|
|
91
|
+
})
|
|
92
|
+
.nullable()
|
|
93
|
+
.optional(),
|
|
94
|
+
check_suite: z
|
|
95
|
+
.object({
|
|
96
|
+
workflow_run: z
|
|
97
|
+
.object({
|
|
98
|
+
id: z.number().nullable().optional(),
|
|
99
|
+
})
|
|
100
|
+
.nullable()
|
|
101
|
+
.optional(),
|
|
102
|
+
})
|
|
103
|
+
.nullable()
|
|
104
|
+
.optional(),
|
|
105
|
+
});
|
|
106
|
+
const GitHubCheckAnnotationSchema = z.object({
|
|
107
|
+
path: z.string().optional(),
|
|
108
|
+
start_line: z.number().optional(),
|
|
109
|
+
end_line: z.number().optional(),
|
|
110
|
+
annotation_level: z.string().optional(),
|
|
111
|
+
message: z.string().optional(),
|
|
112
|
+
title: z.string().optional(),
|
|
113
|
+
raw_details: z.string().optional(),
|
|
114
|
+
});
|
|
115
|
+
const GitHubCheckAnnotationsSchema = z.array(GitHubCheckAnnotationSchema).catch([]);
|
|
116
|
+
const GitHubActionsJobSchema = z.object({
|
|
117
|
+
id: z.number(),
|
|
118
|
+
name: z.string().catch(""),
|
|
119
|
+
status: z.string().nullable().optional(),
|
|
120
|
+
conclusion: z.string().nullable().optional(),
|
|
121
|
+
html_url: z.string().nullable().optional(),
|
|
122
|
+
completed_at: z.string().nullable().optional(),
|
|
123
|
+
});
|
|
124
|
+
const GitHubActionsJobsSchema = z.object({
|
|
125
|
+
jobs: z.array(GitHubActionsJobSchema).catch([]),
|
|
126
|
+
});
|
|
72
127
|
const PullRequestReviewDecisionSchema = z
|
|
73
128
|
.enum(["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"])
|
|
74
129
|
.nullable()
|
|
@@ -142,6 +197,7 @@ const TimelineAuthorSchema = z
|
|
|
142
197
|
.object({
|
|
143
198
|
login: z.string().optional(),
|
|
144
199
|
url: z.string().nullable().optional(),
|
|
200
|
+
avatarUrl: z.string().nullable().optional(),
|
|
145
201
|
})
|
|
146
202
|
.nullable()
|
|
147
203
|
.optional();
|
|
@@ -160,6 +216,27 @@ const PullRequestTimelineCommentNodeSchema = z.object({
|
|
|
160
216
|
createdAt: z.string().nullable().catch(null),
|
|
161
217
|
author: TimelineAuthorSchema,
|
|
162
218
|
});
|
|
219
|
+
const PullRequestReviewThreadCommentNodeSchema = PullRequestTimelineCommentNodeSchema.extend({
|
|
220
|
+
pullRequestReview: z
|
|
221
|
+
.object({ id: z.string().catch("") })
|
|
222
|
+
.nullable()
|
|
223
|
+
.optional()
|
|
224
|
+
.catch(null),
|
|
225
|
+
});
|
|
226
|
+
const PullRequestReviewThreadNodeSchema = z.object({
|
|
227
|
+
id: z.string().catch(""),
|
|
228
|
+
path: z.string().catch(""),
|
|
229
|
+
line: z.number().nullable().optional().catch(null),
|
|
230
|
+
startLine: z.number().nullable().optional().catch(null),
|
|
231
|
+
isResolved: z.boolean().catch(false),
|
|
232
|
+
isOutdated: z.boolean().catch(false),
|
|
233
|
+
comments: z
|
|
234
|
+
.object({
|
|
235
|
+
nodes: z.array(PullRequestReviewThreadCommentNodeSchema).catch([]),
|
|
236
|
+
pageInfo: z.object({ hasNextPage: z.boolean().catch(false) }).catch({ hasNextPage: false }),
|
|
237
|
+
})
|
|
238
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
239
|
+
});
|
|
163
240
|
const PullRequestTimelinePageInfoSchema = z.object({
|
|
164
241
|
hasNextPage: z.boolean().catch(false),
|
|
165
242
|
});
|
|
@@ -183,6 +260,12 @@ const PullRequestTimelineGraphqlSchema = z.object({
|
|
|
183
260
|
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
184
261
|
})
|
|
185
262
|
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
263
|
+
reviewThreads: z
|
|
264
|
+
.object({
|
|
265
|
+
nodes: z.array(PullRequestReviewThreadNodeSchema).catch([]),
|
|
266
|
+
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
267
|
+
})
|
|
268
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
186
269
|
})
|
|
187
270
|
.nullable()
|
|
188
271
|
.optional(),
|
|
@@ -301,6 +384,7 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!) {
|
|
|
301
384
|
author {
|
|
302
385
|
login
|
|
303
386
|
url
|
|
387
|
+
avatarUrl
|
|
304
388
|
}
|
|
305
389
|
}
|
|
306
390
|
pageInfo {
|
|
@@ -316,6 +400,39 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!) {
|
|
|
316
400
|
author {
|
|
317
401
|
login
|
|
318
402
|
url
|
|
403
|
+
avatarUrl
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
pageInfo {
|
|
407
|
+
hasNextPage
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
reviewThreads(first: 100) {
|
|
411
|
+
nodes {
|
|
412
|
+
id
|
|
413
|
+
path
|
|
414
|
+
line
|
|
415
|
+
startLine
|
|
416
|
+
isResolved
|
|
417
|
+
isOutdated
|
|
418
|
+
comments(first: 100) {
|
|
419
|
+
nodes {
|
|
420
|
+
id
|
|
421
|
+
body
|
|
422
|
+
url
|
|
423
|
+
createdAt
|
|
424
|
+
author {
|
|
425
|
+
login
|
|
426
|
+
url
|
|
427
|
+
avatarUrl
|
|
428
|
+
}
|
|
429
|
+
pullRequestReview {
|
|
430
|
+
id
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
pageInfo {
|
|
434
|
+
hasNextPage
|
|
435
|
+
}
|
|
319
436
|
}
|
|
320
437
|
}
|
|
321
438
|
pageInfo {
|
|
@@ -362,6 +479,7 @@ export function createGitHubService(options = {}) {
|
|
|
362
479
|
const cache = new Map();
|
|
363
480
|
const inFlight = new Map();
|
|
364
481
|
const pollTargets = new Map();
|
|
482
|
+
const checkLogTailCache = new Map();
|
|
365
483
|
let api;
|
|
366
484
|
async function cached(params) {
|
|
367
485
|
if (params.readOptions?.force && !params.readOptions.reason) {
|
|
@@ -651,6 +769,79 @@ export function createGitHubService(options = {}) {
|
|
|
651
769
|
},
|
|
652
770
|
});
|
|
653
771
|
},
|
|
772
|
+
getGitHubCheckDetails(input) {
|
|
773
|
+
return cached({
|
|
774
|
+
cwd: input.cwd,
|
|
775
|
+
method: "getGitHubCheckDetails",
|
|
776
|
+
args: {
|
|
777
|
+
repoOwner: input.repoOwner,
|
|
778
|
+
repoName: input.repoName,
|
|
779
|
+
checkRunId: input.checkRunId,
|
|
780
|
+
workflowRunId: input.workflowRunId,
|
|
781
|
+
},
|
|
782
|
+
readOptions: input,
|
|
783
|
+
load: async () => {
|
|
784
|
+
const repoPath = `repos/${input.repoOwner}/${input.repoName}`;
|
|
785
|
+
const checkRun = parseGitHubCheckRunDetails(await run(["api", `${repoPath}/check-runs/${input.checkRunId}`], { cwd: input.cwd }));
|
|
786
|
+
const annotations = parseGitHubCheckAnnotations(await run([
|
|
787
|
+
"api",
|
|
788
|
+
`${repoPath}/check-runs/${input.checkRunId}/annotations`,
|
|
789
|
+
"-f",
|
|
790
|
+
`per_page=${CHECK_ANNOTATION_PAGE_MAX}`,
|
|
791
|
+
], {
|
|
792
|
+
cwd: input.cwd,
|
|
793
|
+
}));
|
|
794
|
+
const workflowRunId = input.workflowRunId ?? checkRun.workflowRunId ?? null;
|
|
795
|
+
const failedJobs = [];
|
|
796
|
+
let truncated = annotations.length >= CHECK_ANNOTATION_PAGE_MAX;
|
|
797
|
+
if (typeof workflowRunId === "number") {
|
|
798
|
+
const jobs = parseGitHubActionsJobs(await run([
|
|
799
|
+
"api",
|
|
800
|
+
`${repoPath}/actions/runs/${workflowRunId}/jobs`,
|
|
801
|
+
"-f",
|
|
802
|
+
`per_page=${ACTIONS_JOB_PAGE_MAX}`,
|
|
803
|
+
], {
|
|
804
|
+
cwd: input.cwd,
|
|
805
|
+
}));
|
|
806
|
+
const failed = jobs.filter(isFailedActionsJob);
|
|
807
|
+
truncated || (truncated = jobs.length >= ACTIONS_JOB_PAGE_MAX);
|
|
808
|
+
truncated || (truncated = failed.length > FAILED_CHECK_JOB_LIMIT);
|
|
809
|
+
for (const job of failed.slice(0, FAILED_CHECK_JOB_LIMIT)) {
|
|
810
|
+
const log = await getCachedCheckLogTail({
|
|
811
|
+
cwd: input.cwd,
|
|
812
|
+
repoPath,
|
|
813
|
+
job,
|
|
814
|
+
run,
|
|
815
|
+
cache: checkLogTailCache,
|
|
816
|
+
});
|
|
817
|
+
truncated || (truncated = log.logTruncated);
|
|
818
|
+
failedJobs.push({
|
|
819
|
+
jobId: job.jobId,
|
|
820
|
+
name: job.name,
|
|
821
|
+
status: job.status,
|
|
822
|
+
conclusion: job.conclusion,
|
|
823
|
+
url: job.url,
|
|
824
|
+
logTail: log.logTail,
|
|
825
|
+
logTruncated: log.logTruncated,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
checkRunId: checkRun.checkRunId,
|
|
831
|
+
workflowRunId,
|
|
832
|
+
name: checkRun.name,
|
|
833
|
+
status: checkRun.status,
|
|
834
|
+
conclusion: checkRun.conclusion,
|
|
835
|
+
url: checkRun.url,
|
|
836
|
+
detailsUrl: checkRun.detailsUrl,
|
|
837
|
+
output: checkRun.output,
|
|
838
|
+
annotations,
|
|
839
|
+
failedJobs,
|
|
840
|
+
truncated,
|
|
841
|
+
};
|
|
842
|
+
},
|
|
843
|
+
});
|
|
844
|
+
},
|
|
654
845
|
async searchIssuesAndPrs(input) {
|
|
655
846
|
if (input.force && !input.reason) {
|
|
656
847
|
throw new Error("GitHubService forced read requires a reason");
|
|
@@ -1356,10 +1547,17 @@ function parseIssueSummaries(stdout) {
|
|
|
1356
1547
|
function parsePullRequestTimeline(stdout, identity) {
|
|
1357
1548
|
const parsed = PullRequestTimelineGraphqlSchema.parse(JSON.parse(stdout || "{}"));
|
|
1358
1549
|
const pullRequest = parsed.data?.repository?.pullRequest;
|
|
1550
|
+
const reviewThreadItems = pullRequest
|
|
1551
|
+
? pullRequest.reviewThreads.nodes.flatMap(toPullRequestTimelineReviewThreadItems)
|
|
1552
|
+
: [];
|
|
1553
|
+
const reviewThreadItemIds = new Set(reviewThreadItems.map((item) => item.id).filter((id) => id.length > 0));
|
|
1359
1554
|
const items = pullRequest
|
|
1360
1555
|
? [
|
|
1361
1556
|
...pullRequest.reviews.nodes.flatMap(toPullRequestTimelineReviewItem),
|
|
1362
|
-
...pullRequest.comments.nodes
|
|
1557
|
+
...pullRequest.comments.nodes
|
|
1558
|
+
.filter((comment) => !reviewThreadItemIds.has(comment.id))
|
|
1559
|
+
.map(toPullRequestTimelineCommentItem),
|
|
1560
|
+
...reviewThreadItems,
|
|
1363
1561
|
].sort(compareTimelineItems)
|
|
1364
1562
|
: [];
|
|
1365
1563
|
return {
|
|
@@ -1367,8 +1565,11 @@ function parsePullRequestTimeline(stdout, identity) {
|
|
|
1367
1565
|
repoOwner: identity.repoOwner,
|
|
1368
1566
|
repoName: identity.repoName,
|
|
1369
1567
|
items,
|
|
1370
|
-
// S3 deliberately caps timeline fetches at the first 100 reviews and
|
|
1371
|
-
truncated: Boolean(pullRequest?.reviews.pageInfo.hasNextPage ||
|
|
1568
|
+
// S3 deliberately caps timeline fetches at the first 100 reviews, comments, and review threads.
|
|
1569
|
+
truncated: Boolean(pullRequest?.reviews.pageInfo.hasNextPage ||
|
|
1570
|
+
pullRequest?.comments.pageInfo.hasNextPage ||
|
|
1571
|
+
pullRequest?.reviewThreads.pageInfo.hasNextPage ||
|
|
1572
|
+
pullRequest?.reviewThreads.nodes.some((thread) => thread.comments.pageInfo.hasNextPage)),
|
|
1372
1573
|
error: pullRequest ? null : { kind: "not_found", message: "Pull request not found" },
|
|
1373
1574
|
};
|
|
1374
1575
|
}
|
|
@@ -1383,6 +1584,7 @@ function toPullRequestTimelineReviewItem(review) {
|
|
|
1383
1584
|
id: review.id,
|
|
1384
1585
|
author: review.author?.login ?? "unknown",
|
|
1385
1586
|
authorUrl: review.author?.url ?? null,
|
|
1587
|
+
avatarUrl: review.author?.avatarUrl ?? null,
|
|
1386
1588
|
body: review.body ?? "",
|
|
1387
1589
|
createdAt: parseOptionalTime(review.submittedAt ?? null),
|
|
1388
1590
|
url: review.url,
|
|
@@ -1396,11 +1598,129 @@ function toPullRequestTimelineCommentItem(comment) {
|
|
|
1396
1598
|
id: comment.id,
|
|
1397
1599
|
author: comment.author?.login ?? "unknown",
|
|
1398
1600
|
authorUrl: comment.author?.url ?? null,
|
|
1601
|
+
avatarUrl: comment.author?.avatarUrl ?? null,
|
|
1399
1602
|
body: comment.body ?? "",
|
|
1400
1603
|
createdAt: parseOptionalTime(comment.createdAt ?? null),
|
|
1401
1604
|
url: comment.url,
|
|
1402
1605
|
};
|
|
1403
1606
|
}
|
|
1607
|
+
function toPullRequestTimelineReviewThreadItems(thread) {
|
|
1608
|
+
return thread.comments.nodes.map((comment) => ({
|
|
1609
|
+
...toPullRequestTimelineCommentItem(comment),
|
|
1610
|
+
...(comment.pullRequestReview?.id ? { reviewId: comment.pullRequestReview.id } : {}),
|
|
1611
|
+
location: {
|
|
1612
|
+
path: thread.path,
|
|
1613
|
+
...(thread.line !== null && thread.line !== undefined ? { line: thread.line } : {}),
|
|
1614
|
+
...(thread.startLine !== null && thread.startLine !== undefined
|
|
1615
|
+
? { startLine: thread.startLine }
|
|
1616
|
+
: {}),
|
|
1617
|
+
...(thread.id ? { threadId: thread.id } : {}),
|
|
1618
|
+
isResolved: thread.isResolved,
|
|
1619
|
+
isOutdated: thread.isOutdated,
|
|
1620
|
+
},
|
|
1621
|
+
}));
|
|
1622
|
+
}
|
|
1623
|
+
function parseGitHubCheckRunDetails(stdout) {
|
|
1624
|
+
const parsed = GitHubCheckRunDetailsSchema.parse(JSON.parse(stdout || "{}"));
|
|
1625
|
+
return {
|
|
1626
|
+
checkRunId: parsed.id,
|
|
1627
|
+
workflowRunId: parsed.check_suite?.workflow_run?.id ?? null,
|
|
1628
|
+
name: parsed.name,
|
|
1629
|
+
status: parsed.status,
|
|
1630
|
+
conclusion: parsed.conclusion,
|
|
1631
|
+
url: parsed.html_url,
|
|
1632
|
+
detailsUrl: parsed.details_url,
|
|
1633
|
+
output: parsed.output,
|
|
1634
|
+
annotations: [],
|
|
1635
|
+
failedJobs: [],
|
|
1636
|
+
truncated: false,
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function parseGitHubCheckAnnotations(stdout) {
|
|
1640
|
+
return GitHubCheckAnnotationsSchema.parse(JSON.parse(stdout || "[]")).map((annotation) => {
|
|
1641
|
+
const result = {};
|
|
1642
|
+
if (annotation.path)
|
|
1643
|
+
result.path = annotation.path;
|
|
1644
|
+
if (annotation.start_line !== undefined)
|
|
1645
|
+
result.startLine = annotation.start_line;
|
|
1646
|
+
if (annotation.end_line !== undefined)
|
|
1647
|
+
result.endLine = annotation.end_line;
|
|
1648
|
+
if (annotation.annotation_level)
|
|
1649
|
+
result.annotationLevel = annotation.annotation_level;
|
|
1650
|
+
if (annotation.message)
|
|
1651
|
+
result.message = annotation.message;
|
|
1652
|
+
if (annotation.title)
|
|
1653
|
+
result.title = annotation.title;
|
|
1654
|
+
if (annotation.raw_details)
|
|
1655
|
+
result.rawDetails = annotation.raw_details;
|
|
1656
|
+
return result;
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
function parseGitHubActionsJobs(stdout) {
|
|
1660
|
+
return GitHubActionsJobsSchema.parse(JSON.parse(stdout || "{}")).jobs.map((job) => {
|
|
1661
|
+
const result = {
|
|
1662
|
+
jobId: job.id,
|
|
1663
|
+
name: job.name,
|
|
1664
|
+
status: job.status,
|
|
1665
|
+
conclusion: job.conclusion,
|
|
1666
|
+
url: job.html_url,
|
|
1667
|
+
};
|
|
1668
|
+
if (job.completed_at)
|
|
1669
|
+
result.completedAt = job.completed_at;
|
|
1670
|
+
return result;
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
function isFailedActionsJob(job) {
|
|
1674
|
+
return (job.conclusion === "failure" ||
|
|
1675
|
+
job.conclusion === "cancelled" ||
|
|
1676
|
+
job.conclusion === "timed_out" ||
|
|
1677
|
+
job.conclusion === "action_required");
|
|
1678
|
+
}
|
|
1679
|
+
async function getCachedCheckLogTail(input) {
|
|
1680
|
+
const key = `${input.job.jobId}:${input.job.completedAt ?? ""}`;
|
|
1681
|
+
const cached = input.cache.get(key);
|
|
1682
|
+
if (cached) {
|
|
1683
|
+
input.cache.delete(key);
|
|
1684
|
+
input.cache.set(key, cached);
|
|
1685
|
+
return cached;
|
|
1686
|
+
}
|
|
1687
|
+
const capped = capCheckLogTail(await input.run(["api", `${input.repoPath}/actions/jobs/${input.job.jobId}/logs`], {
|
|
1688
|
+
cwd: input.cwd,
|
|
1689
|
+
}));
|
|
1690
|
+
input.cache.set(key, capped);
|
|
1691
|
+
while (input.cache.size > CHECK_LOG_TAIL_CACHE_MAX_ENTRIES) {
|
|
1692
|
+
const oldestKey = input.cache.keys().next().value;
|
|
1693
|
+
if (!oldestKey) {
|
|
1694
|
+
break;
|
|
1695
|
+
}
|
|
1696
|
+
input.cache.delete(oldestKey);
|
|
1697
|
+
}
|
|
1698
|
+
return capped;
|
|
1699
|
+
}
|
|
1700
|
+
function capCheckLogTail(log) {
|
|
1701
|
+
const lines = log.split("\n");
|
|
1702
|
+
let truncated = lines.length > CHECK_LOG_TAIL_MAX_LINES;
|
|
1703
|
+
let tail = lines.slice(-CHECK_LOG_TAIL_MAX_LINES).join("\n");
|
|
1704
|
+
if (Buffer.byteLength(tail, "utf8") > CHECK_LOG_TAIL_MAX_BYTES) {
|
|
1705
|
+
truncated = true;
|
|
1706
|
+
tail = utf8SuffixWithinBytes(tail, CHECK_LOG_TAIL_MAX_BYTES);
|
|
1707
|
+
}
|
|
1708
|
+
return { logTail: tail, logTruncated: truncated };
|
|
1709
|
+
}
|
|
1710
|
+
function utf8SuffixWithinBytes(value, maxBytes) {
|
|
1711
|
+
let lowerBound = 0;
|
|
1712
|
+
let upperBound = value.length;
|
|
1713
|
+
while (lowerBound < upperBound) {
|
|
1714
|
+
const midpoint = Math.floor((lowerBound + upperBound) / 2);
|
|
1715
|
+
if (Buffer.byteLength(value.slice(midpoint), "utf8") > maxBytes) {
|
|
1716
|
+
lowerBound = midpoint + 1;
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
upperBound = midpoint;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return value.slice(lowerBound);
|
|
1723
|
+
}
|
|
1404
1724
|
function mapTimelineReviewState(state, body) {
|
|
1405
1725
|
switch (state) {
|
|
1406
1726
|
case "APPROVED":
|
|
@@ -1541,6 +1861,10 @@ function buildPullRequestCheck(context) {
|
|
|
1541
1861
|
...(typeof context.workflowName === "string" && context.workflowName.trim().length > 0
|
|
1542
1862
|
? { workflow: context.workflowName }
|
|
1543
1863
|
: {}),
|
|
1864
|
+
...(typeof context.databaseId === "number" ? { checkRunId: context.databaseId } : {}),
|
|
1865
|
+
...(typeof context.checkSuite?.workflowRun?.databaseId === "number"
|
|
1866
|
+
? { workflowRunId: context.checkSuite.workflowRun.databaseId }
|
|
1867
|
+
: {}),
|
|
1544
1868
|
...formatCheckRunDuration(context),
|
|
1545
1869
|
recency: getCheckRunRecency(context),
|
|
1546
1870
|
};
|
|
@@ -9,6 +9,7 @@ export interface TerminalSessionControllerOptions {
|
|
|
9
9
|
hasBinaryChannel: () => boolean;
|
|
10
10
|
isPathWithinRoot: (rootPath: string, candidatePath: string) => boolean;
|
|
11
11
|
sessionLogger: pino.Logger;
|
|
12
|
+
listTerminalWorkspaceRoots?: () => Promise<readonly string[]>;
|
|
12
13
|
clientSupportsWrapReflow?: () => boolean;
|
|
13
14
|
}
|
|
14
15
|
export interface TerminalSessionControllerMetrics {
|
|
@@ -22,6 +23,7 @@ export declare class TerminalSessionController {
|
|
|
22
23
|
private readonly hasBinaryChannel;
|
|
23
24
|
private readonly isPathWithinRoot;
|
|
24
25
|
private readonly sessionLogger;
|
|
26
|
+
private readonly listTerminalWorkspaceRoots;
|
|
25
27
|
private readonly clientSupportsWrapReflow;
|
|
26
28
|
private readonly subscribedDirectories;
|
|
27
29
|
private unsubscribeTerminalsChanged;
|
|
@@ -50,6 +52,10 @@ export declare class TerminalSessionController {
|
|
|
50
52
|
private emitTerminalsSnapshotForRoot;
|
|
51
53
|
private handleListTerminalsRequest;
|
|
52
54
|
private getAllTerminalSessions;
|
|
55
|
+
private getTerminalsForWorkspaceRoot;
|
|
56
|
+
private terminalBelongsToRoot;
|
|
57
|
+
private resolveTerminalOwnerRoot;
|
|
58
|
+
private isSamePath;
|
|
53
59
|
private handleCreateTerminalRequest;
|
|
54
60
|
private handleRenameTerminalRequest;
|
|
55
61
|
private handleSubscribeTerminalRequest;
|
|
@@ -29,6 +29,7 @@ export class TerminalSessionController {
|
|
|
29
29
|
this.hasBinaryChannel = options.hasBinaryChannel;
|
|
30
30
|
this.isPathWithinRoot = options.isPathWithinRoot;
|
|
31
31
|
this.sessionLogger = options.sessionLogger;
|
|
32
|
+
this.listTerminalWorkspaceRoots = options.listTerminalWorkspaceRoots ?? (async () => []);
|
|
32
33
|
this.clientSupportsWrapReflow = options.clientSupportsWrapReflow ?? (() => false);
|
|
33
34
|
}
|
|
34
35
|
start() {
|
|
@@ -199,7 +200,7 @@ export class TerminalSessionController {
|
|
|
199
200
|
return;
|
|
200
201
|
}
|
|
201
202
|
try {
|
|
202
|
-
const terminals = await this.
|
|
203
|
+
const terminals = await this.getTerminalsForWorkspaceRoot(cwd);
|
|
203
204
|
for (const terminal of terminals) {
|
|
204
205
|
this.ensureExitSubscription(terminal);
|
|
205
206
|
}
|
|
@@ -229,7 +230,7 @@ export class TerminalSessionController {
|
|
|
229
230
|
}
|
|
230
231
|
try {
|
|
231
232
|
const terminals = typeof msg.cwd === "string"
|
|
232
|
-
? await this.
|
|
233
|
+
? await this.getTerminalsForWorkspaceRoot(msg.cwd)
|
|
233
234
|
: await this.getAllTerminalSessions();
|
|
234
235
|
for (const terminal of terminals) {
|
|
235
236
|
this.ensureExitSubscription(terminal);
|
|
@@ -264,6 +265,39 @@ export class TerminalSessionController {
|
|
|
264
265
|
const terminalsByDirectory = await Promise.all(directories.map((cwd) => manager.getTerminals(cwd)));
|
|
265
266
|
return terminalsByDirectory.flat();
|
|
266
267
|
}
|
|
268
|
+
async getTerminalsForWorkspaceRoot(cwd) {
|
|
269
|
+
if (!this.terminalManager) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
const terminals = await this.terminalManager.getTerminals(cwd);
|
|
273
|
+
const workspaceRoots = await this.listTerminalWorkspaceRoots();
|
|
274
|
+
if (workspaceRoots.length === 0) {
|
|
275
|
+
return terminals;
|
|
276
|
+
}
|
|
277
|
+
return terminals.filter((terminal) => this.terminalBelongsToRoot(cwd, terminal.cwd, workspaceRoots));
|
|
278
|
+
}
|
|
279
|
+
terminalBelongsToRoot(rootCwd, terminalCwd, workspaceRoots) {
|
|
280
|
+
const ownerRoot = this.resolveTerminalOwnerRoot(terminalCwd, workspaceRoots);
|
|
281
|
+
if (!ownerRoot) {
|
|
282
|
+
return this.isPathWithinRoot(rootCwd, terminalCwd);
|
|
283
|
+
}
|
|
284
|
+
return this.isSamePath(rootCwd, ownerRoot);
|
|
285
|
+
}
|
|
286
|
+
resolveTerminalOwnerRoot(terminalCwd, workspaceRoots) {
|
|
287
|
+
let ownerRoot = null;
|
|
288
|
+
for (const workspaceRoot of workspaceRoots) {
|
|
289
|
+
if (!this.isPathWithinRoot(workspaceRoot, terminalCwd)) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (!ownerRoot || workspaceRoot.length > ownerRoot.length) {
|
|
293
|
+
ownerRoot = workspaceRoot;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return ownerRoot;
|
|
297
|
+
}
|
|
298
|
+
isSamePath(firstPath, secondPath) {
|
|
299
|
+
return (this.isPathWithinRoot(firstPath, secondPath) && this.isPathWithinRoot(secondPath, firstPath));
|
|
300
|
+
}
|
|
267
301
|
async handleCreateTerminalRequest(msg) {
|
|
268
302
|
if (!this.terminalManager) {
|
|
269
303
|
this.emit({
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { AgentProviderRuntimeSettingsMapSchema, migrateProviderSettings, ProviderOverridesSchema, } from "./agent/provider-launch-config.js";
|
|
5
5
|
import { ensurePrivateFile, writePrivateFileAtomicSync } from "./private-files.js";
|
|
6
|
+
import { TerminalProfileSchema } from "@getpaseo/protocol/messages";
|
|
6
7
|
export const LogLevelSchema = z.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
|
|
7
8
|
export const LogFormatSchema = z.enum(["pretty", "json"]);
|
|
8
9
|
const LogConfigSchema = z
|
|
@@ -187,6 +188,7 @@ export const PersistedConfigSchema = z
|
|
|
187
188
|
.optional(),
|
|
188
189
|
autoArchiveAfterMerge: z.boolean().optional(),
|
|
189
190
|
appendSystemPrompt: z.string().optional(),
|
|
191
|
+
terminalProfiles: z.array(TerminalProfileSchema).optional(),
|
|
190
192
|
cors: z
|
|
191
193
|
.object({
|
|
192
194
|
allowedOrigins: z.array(z.string()).optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.94",
|
|
4
4
|
"description": "Paseo backend server",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/server",
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@agentclientprotocol/sdk": "^0.17.1",
|
|
61
61
|
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
|
|
62
|
-
"@getpaseo/client": "0.1.
|
|
63
|
-
"@getpaseo/highlight": "0.1.
|
|
64
|
-
"@getpaseo/protocol": "0.1.
|
|
65
|
-
"@getpaseo/relay": "0.1.
|
|
62
|
+
"@getpaseo/client": "0.1.94",
|
|
63
|
+
"@getpaseo/highlight": "0.1.94",
|
|
64
|
+
"@getpaseo/protocol": "0.1.94",
|
|
65
|
+
"@getpaseo/relay": "0.1.94",
|
|
66
66
|
"@isaacs/ttlcache": "^2.1.4",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
68
68
|
"@opencode-ai/sdk": "1.14.46",
|