@parall/sdk 1.20.1 → 1.21.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/client.d.ts +66 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +139 -3
- package/dist/constants.d.ts +19 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +86 -2
- package/dist/types.d.ts +155 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +223 -13
- package/src/constants.ts +96 -3
- package/src/types.ts +188 -10
package/src/client.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
RegisterRequest,
|
|
5
5
|
RegisterResponse,
|
|
6
6
|
LoginRequest,
|
|
7
|
+
ChangePasswordRequest,
|
|
7
8
|
CheckEmailResponse,
|
|
8
9
|
User,
|
|
9
10
|
Organization,
|
|
@@ -44,11 +45,16 @@ import type {
|
|
|
44
45
|
Project,
|
|
45
46
|
CreateProjectRequest,
|
|
46
47
|
UpdateProjectRequest,
|
|
48
|
+
Schedule,
|
|
49
|
+
ScheduleRun,
|
|
50
|
+
CreateScheduleInput,
|
|
51
|
+
UpdateScheduleInput,
|
|
52
|
+
ScheduleFilters,
|
|
53
|
+
ScheduleRunFilters,
|
|
47
54
|
AgentSessionDB,
|
|
48
55
|
AgentStep,
|
|
49
56
|
CreateAgentSessionRequest,
|
|
50
57
|
CreateAgentStepRequest,
|
|
51
|
-
CreateAgentTaskBackfillCommentRequest,
|
|
52
58
|
UpdateAgentSessionRequest,
|
|
53
59
|
OrgInvitation,
|
|
54
60
|
InvitationPublicInfo,
|
|
@@ -56,12 +62,16 @@ import type {
|
|
|
56
62
|
Wiki,
|
|
57
63
|
WikiBlob,
|
|
58
64
|
WikiChangeset,
|
|
65
|
+
WikiFileUploadResponse,
|
|
66
|
+
WikiFilePreviewUrlResponse,
|
|
59
67
|
WikiTreeResponse,
|
|
60
68
|
WikiNodeSectionArtifact,
|
|
61
69
|
WikiSearchResponse,
|
|
62
70
|
WikiPageIndex,
|
|
63
71
|
WikiRefsResponse,
|
|
64
72
|
WikiRefsCheckResponse,
|
|
73
|
+
WikiAnchorStatusRequest,
|
|
74
|
+
WikiAnchorStatusResponse,
|
|
65
75
|
WikiDiff,
|
|
66
76
|
WikiPathScope,
|
|
67
77
|
WikiAccessStatus,
|
|
@@ -290,6 +300,68 @@ export class ParallClient {
|
|
|
290
300
|
return res.json() as Promise<T>;
|
|
291
301
|
}
|
|
292
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Multipart upload variant of `request`. Same auth / refresh / error
|
|
305
|
+
* handling, but lets the caller hand us a prepared `FormData` (file +
|
|
306
|
+
* text fields) and skips the JSON content-type. Used by
|
|
307
|
+
* uploadWikiFile / uploadWikiFileToChangeset — wiki binary uploads can
|
|
308
|
+
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
309
|
+
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
310
|
+
*/
|
|
311
|
+
private async multipartRequest<T>(
|
|
312
|
+
method: string,
|
|
313
|
+
path: string,
|
|
314
|
+
body: FormData,
|
|
315
|
+
retried = false,
|
|
316
|
+
): Promise<T> {
|
|
317
|
+
if (!retried) {
|
|
318
|
+
await this.ensureFreshToken(path);
|
|
319
|
+
}
|
|
320
|
+
// Don't set Content-Type — the browser fills in the boundary.
|
|
321
|
+
const { 'Content-Type': _drop, ...headers } = this.buildHeaders();
|
|
322
|
+
void _drop;
|
|
323
|
+
|
|
324
|
+
let res: Response;
|
|
325
|
+
try {
|
|
326
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
327
|
+
method,
|
|
328
|
+
headers,
|
|
329
|
+
body,
|
|
330
|
+
signal: AbortSignal.timeout(5 * 60 * 1000),
|
|
331
|
+
});
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (err instanceof DOMException && err.name === 'TimeoutError') {
|
|
334
|
+
throw new ApiError(0, 'Request timed out', 'REQUEST_TIMEOUT');
|
|
335
|
+
}
|
|
336
|
+
throw err;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (res.status === 401) {
|
|
340
|
+
const pathSuffix = path.replace(/^\/api\/v1/, '');
|
|
341
|
+
const isAuthPath = ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
342
|
+
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
343
|
+
const refreshed = await this.tryRefresh();
|
|
344
|
+
if (refreshed) {
|
|
345
|
+
return this.multipartRequest<T>(method, path, body, true);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (this.onTokenExpired && !isAuthPath) {
|
|
349
|
+
this.onTokenExpired();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!res.ok) {
|
|
354
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
355
|
+
const errorObj = errorBody?.error && typeof errorBody.error === 'object' ? errorBody.error as { message?: string; code?: string } : undefined;
|
|
356
|
+
const errMsg = errorObj?.message ?? (typeof errorBody?.error === 'string' ? errorBody.error : undefined);
|
|
357
|
+
const errCode = errorObj?.code ?? (typeof errorBody?.code === 'string' ? errorBody.code : undefined);
|
|
358
|
+
throw new ApiError(res.status, errMsg ?? res.statusText, errCode);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (res.status === 204) return undefined as T;
|
|
362
|
+
return res.json() as Promise<T>;
|
|
363
|
+
}
|
|
364
|
+
|
|
293
365
|
// ---- Auth ----
|
|
294
366
|
|
|
295
367
|
async register(req: RegisterRequest): Promise<RegisterResponse> {
|
|
@@ -308,6 +380,10 @@ export class ParallClient {
|
|
|
308
380
|
return this.request('POST', ENDPOINTS.AUTH_LOGOUT);
|
|
309
381
|
}
|
|
310
382
|
|
|
383
|
+
async changePassword(req: ChangePasswordRequest): Promise<void> {
|
|
384
|
+
return this.request('POST', ENDPOINTS.AUTH_CHANGE_PASSWORD, req);
|
|
385
|
+
}
|
|
386
|
+
|
|
311
387
|
async checkEmail(email: string): Promise<CheckEmailResponse> {
|
|
312
388
|
return this.request('POST', ENDPOINTS.AUTH_CHECK_EMAIL, { email });
|
|
313
389
|
}
|
|
@@ -679,15 +755,6 @@ export class ParallClient {
|
|
|
679
755
|
return this.request('POST', ENDPOINTS.AGENT_SESSION_STEPS(orgId, agentId, sessionId), req);
|
|
680
756
|
}
|
|
681
757
|
|
|
682
|
-
async createAgentTaskBackfillComment(
|
|
683
|
-
orgId: string,
|
|
684
|
-
agentId: string,
|
|
685
|
-
sessionId: string,
|
|
686
|
-
req: CreateAgentTaskBackfillCommentRequest,
|
|
687
|
-
): Promise<Comment> {
|
|
688
|
-
return this.request('POST', ENDPOINTS.AGENT_SESSION_TASK_BACKFILL_COMMENTS(orgId, agentId, sessionId), req);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
758
|
async getAgentSessionSteps(orgId: string, agentId: string, sessionId: string, params?: { limit?: number }): Promise<AgentStep[]> {
|
|
692
759
|
const res = await this.request<{ data: AgentStep[] }>('GET', ENDPOINTS.AGENT_SESSION_STEPS(orgId, agentId, sessionId), undefined, params);
|
|
693
760
|
return res.data;
|
|
@@ -1067,6 +1134,57 @@ export class ParallClient {
|
|
|
1067
1134
|
return this.request('DELETE', ENDPOINTS.PROJECT(orgId, projectId));
|
|
1068
1135
|
}
|
|
1069
1136
|
|
|
1137
|
+
// ---- Schedules (org-scoped) ----
|
|
1138
|
+
|
|
1139
|
+
async createSchedule(orgId: string, input: CreateScheduleInput): Promise<Schedule> {
|
|
1140
|
+
return this.request('POST', ENDPOINTS.SCHEDULES(orgId), input);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
async listSchedules(orgId: string, filters?: ScheduleFilters): Promise<PaginatedResponse<Schedule>> {
|
|
1144
|
+
return this.request('GET', ENDPOINTS.SCHEDULES(orgId), undefined, filters as Record<string, string | number | undefined>);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
async getSchedule(orgId: string, scheduleId: string): Promise<Schedule> {
|
|
1148
|
+
return this.request('GET', ENDPOINTS.SCHEDULE(orgId, scheduleId));
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
async updateSchedule(orgId: string, scheduleId: string, patch: UpdateScheduleInput): Promise<Schedule> {
|
|
1152
|
+
return this.request('PATCH', ENDPOINTS.SCHEDULE(orgId, scheduleId), patch);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async pauseSchedule(orgId: string, scheduleId: string): Promise<Schedule> {
|
|
1156
|
+
return this.request('POST', ENDPOINTS.SCHEDULE_PAUSE(orgId, scheduleId));
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
async resumeSchedule(orgId: string, scheduleId: string): Promise<Schedule> {
|
|
1160
|
+
return this.request('POST', ENDPOINTS.SCHEDULE_RESUME(orgId, scheduleId));
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async cancelSchedule(orgId: string, scheduleId: string): Promise<Schedule> {
|
|
1164
|
+
return this.request('POST', ENDPOINTS.SCHEDULE_CANCEL(orgId, scheduleId));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
async deleteSchedule(orgId: string, scheduleId: string): Promise<void> {
|
|
1168
|
+
return this.request('DELETE', ENDPOINTS.SCHEDULE(orgId, scheduleId));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
async listScheduleRuns(
|
|
1172
|
+
orgId: string,
|
|
1173
|
+
scheduleId: string,
|
|
1174
|
+
filters?: ScheduleRunFilters,
|
|
1175
|
+
): Promise<PaginatedResponse<ScheduleRun>> {
|
|
1176
|
+
return this.request(
|
|
1177
|
+
'GET',
|
|
1178
|
+
ENDPOINTS.SCHEDULE_RUNS(orgId, scheduleId),
|
|
1179
|
+
undefined,
|
|
1180
|
+
filters as Record<string, string | number | undefined>,
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
async getScheduleRun(orgId: string, runId: string): Promise<ScheduleRun> {
|
|
1185
|
+
return this.request('GET', ENDPOINTS.SCHEDULE_RUN(orgId, runId));
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1070
1188
|
// ---- Wikis (org-scoped) ----
|
|
1071
1189
|
|
|
1072
1190
|
async createWiki(orgId: string, data: CreateWikiRequest): Promise<Wiki> {
|
|
@@ -1110,6 +1228,16 @@ export class ParallClient {
|
|
|
1110
1228
|
return this.request('GET', ENDPOINTS.WIKI_REFS_CHECK(orgId, wikiId), undefined, params);
|
|
1111
1229
|
}
|
|
1112
1230
|
|
|
1231
|
+
// Anchor-status: batch blob-SHA compare for inline-comment outdated judging.
|
|
1232
|
+
// Refs the caller cannot read are silently dropped from the response.
|
|
1233
|
+
async getWikiAnchorStatus(
|
|
1234
|
+
orgId: string,
|
|
1235
|
+
wikiId: string,
|
|
1236
|
+
body: WikiAnchorStatusRequest,
|
|
1237
|
+
): Promise<WikiAnchorStatusResponse> {
|
|
1238
|
+
return this.request('POST', ENDPOINTS.WIKI_ANCHOR_STATUS(orgId, wikiId), body);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1113
1241
|
async createWikiChangeset(orgId: string, wikiId: string, data: CreateWikiChangesetRequest): Promise<WikiChangeset> {
|
|
1114
1242
|
return normalizeWikiChangeset(await this.request('POST', ENDPOINTS.WIKI_CHANGESETS(orgId, wikiId), data));
|
|
1115
1243
|
}
|
|
@@ -1144,6 +1272,85 @@ export class ParallClient {
|
|
|
1144
1272
|
return normalizeWikiChangeset(await this.request('POST', ENDPOINTS.WIKI_CHANGESET_CLOSE(orgId, wikiId, changesetId)));
|
|
1145
1273
|
}
|
|
1146
1274
|
|
|
1275
|
+
// ---- Wiki Binary Upload / Delete (Phase 2 of wiki-file-storage-design) ----
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Upload a binary file directly to the wiki's default branch (maintain
|
|
1279
|
+
* scope). Size ≤ 1 MiB lands as an inline Git blob; > 1 MiB lands as a
|
|
1280
|
+
* Git-LFS pointer backed by S3. Text files return 422 — they must go
|
|
1281
|
+
* through createWikiChangeset / updateWikiChangeset.
|
|
1282
|
+
*/
|
|
1283
|
+
async uploadWikiFile(
|
|
1284
|
+
orgId: string,
|
|
1285
|
+
wikiId: string,
|
|
1286
|
+
params: { path: string; file: Blob; message?: string },
|
|
1287
|
+
): Promise<WikiFileUploadResponse> {
|
|
1288
|
+
// `POST /uploads` always writes to the wiki's default branch — the
|
|
1289
|
+
// backend's parseUpload silently drops any `parent_ref` field, so
|
|
1290
|
+
// surfacing one in this signature would mislead callers. To target
|
|
1291
|
+
// a feature branch, use uploadWikiFileToChangeset.
|
|
1292
|
+
const fd = new FormData();
|
|
1293
|
+
fd.append('path', params.path);
|
|
1294
|
+
fd.append('file', params.file);
|
|
1295
|
+
if (params.message) fd.append('message', params.message);
|
|
1296
|
+
return this.multipartRequest('POST', ENDPOINTS.WIKI_UPLOADS(orgId, wikiId), fd);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Upload a binary file into a changeset's feature branch (read scope +
|
|
1301
|
+
* author-only). Used by reader flow: propose a changeset, attach
|
|
1302
|
+
* binary, PATCH markdown that references it. On merge the binary
|
|
1303
|
+
* squashes into the default branch.
|
|
1304
|
+
*/
|
|
1305
|
+
async uploadWikiFileToChangeset(
|
|
1306
|
+
orgId: string,
|
|
1307
|
+
wikiId: string,
|
|
1308
|
+
changesetId: string,
|
|
1309
|
+
params: { path: string; file: Blob; message?: string },
|
|
1310
|
+
): Promise<WikiFileUploadResponse> {
|
|
1311
|
+
const fd = new FormData();
|
|
1312
|
+
fd.append('path', params.path);
|
|
1313
|
+
fd.append('file', params.file);
|
|
1314
|
+
if (params.message) fd.append('message', params.message);
|
|
1315
|
+
return this.multipartRequest(
|
|
1316
|
+
'POST',
|
|
1317
|
+
ENDPOINTS.WIKI_CHANGESET_FILES(orgId, wikiId, changesetId),
|
|
1318
|
+
fd,
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Remove a binary file from the default branch. Text files must use a
|
|
1323
|
+
* changeset delete action. The blob stays reachable via git history. */
|
|
1324
|
+
async deleteWikiFile(
|
|
1325
|
+
orgId: string,
|
|
1326
|
+
wikiId: string,
|
|
1327
|
+
params: { path: string; message?: string },
|
|
1328
|
+
): Promise<void> {
|
|
1329
|
+
await this.request(
|
|
1330
|
+
'DELETE',
|
|
1331
|
+
ENDPOINTS.WIKI_FILES(orgId, wikiId),
|
|
1332
|
+
undefined,
|
|
1333
|
+
{ path: params.path, message: params.message },
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Sign a short-lived (~5-min) URL the browser can paste into a media
|
|
1339
|
+
* element src. ACL is evaluated here; the returned URL is a capability
|
|
1340
|
+
* token — don't leak it.
|
|
1341
|
+
*/
|
|
1342
|
+
async getWikiFilePreviewUrl(
|
|
1343
|
+
orgId: string,
|
|
1344
|
+
wikiId: string,
|
|
1345
|
+
params: { path: string; ref?: string },
|
|
1346
|
+
): Promise<WikiFilePreviewUrlResponse> {
|
|
1347
|
+
return this.request(
|
|
1348
|
+
'POST',
|
|
1349
|
+
ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId),
|
|
1350
|
+
params,
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1147
1354
|
// ---- Wiki Path Scopes (AFCS ACL) ----
|
|
1148
1355
|
|
|
1149
1356
|
async getWikiPathScopes(orgId: string, wikiId: string): Promise<WikiPathScope[]> {
|
|
@@ -1215,9 +1422,12 @@ export class ParallClient {
|
|
|
1215
1422
|
|
|
1216
1423
|
// ---- Unified Comments ----
|
|
1217
1424
|
|
|
1218
|
-
async getComments(
|
|
1219
|
-
|
|
1220
|
-
|
|
1425
|
+
async getComments(
|
|
1426
|
+
orgId: string,
|
|
1427
|
+
params:
|
|
1428
|
+
| { target_uri: string; target_prefix?: never; limit?: number; cursor?: string; order?: string }
|
|
1429
|
+
| { target_prefix: string; target_uri?: never; limit?: number; cursor?: string; order?: string },
|
|
1430
|
+
): Promise<PaginatedResponse<Comment>> {
|
|
1221
1431
|
return this.request('GET', ENDPOINTS.COMMENTS(orgId), undefined, params);
|
|
1222
1432
|
}
|
|
1223
1433
|
|
package/src/constants.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const ENDPOINTS = {
|
|
|
11
11
|
AUTH_LOGIN: `${API_BASE}/auth/login`,
|
|
12
12
|
AUTH_REFRESH: `${API_BASE}/auth/refresh`,
|
|
13
13
|
AUTH_LOGOUT: `${API_BASE}/auth/logout`,
|
|
14
|
+
AUTH_CHANGE_PASSWORD: `${API_BASE}/auth/change-password`,
|
|
14
15
|
AUTH_CHECK_EMAIL: `${API_BASE}/auth/check-email`,
|
|
15
16
|
AUTH_VERIFY_EMAIL: `${API_BASE}/auth/verify-email`,
|
|
16
17
|
AUTH_RESEND_CODE: `${API_BASE}/auth/resend-code`,
|
|
@@ -89,8 +90,6 @@ export const ENDPOINTS = {
|
|
|
89
90
|
`${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/steps`,
|
|
90
91
|
AGENT_SESSION_STEP: (orgId: string, agentId: string, sessionId: string, stepId: string) =>
|
|
91
92
|
`${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/steps/${stepId}`,
|
|
92
|
-
AGENT_SESSION_TASK_BACKFILL_COMMENTS: (orgId: string, agentId: string, sessionId: string) =>
|
|
93
|
-
`${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/task-backfill-comments`,
|
|
94
93
|
AGENT_TASKS: (orgId: string, agentId: string) =>
|
|
95
94
|
`${API_BASE}/orgs/${orgId}/agents/${agentId}/tasks`,
|
|
96
95
|
AGENT_RUNTIME_AUTH: (orgId: string, agentId: string) =>
|
|
@@ -157,6 +156,15 @@ export const ENDPOINTS = {
|
|
|
157
156
|
PROJECTS: (orgId: string) => `${API_BASE}/orgs/${orgId}/projects`,
|
|
158
157
|
PROJECT: (orgId: string, projectId: string) => `${API_BASE}/orgs/${orgId}/projects/${projectId}`,
|
|
159
158
|
|
|
159
|
+
// Schedules (org-scoped, platform time trigger primitive)
|
|
160
|
+
SCHEDULES: (orgId: string) => `${API_BASE}/orgs/${orgId}/schedules`,
|
|
161
|
+
SCHEDULE: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/schedules/${id}`,
|
|
162
|
+
SCHEDULE_PAUSE: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/schedules/${id}/pause`,
|
|
163
|
+
SCHEDULE_RESUME: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/schedules/${id}/resume`,
|
|
164
|
+
SCHEDULE_CANCEL: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/schedules/${id}/cancel`,
|
|
165
|
+
SCHEDULE_RUNS: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/schedules/${id}/runs`,
|
|
166
|
+
SCHEDULE_RUN: (orgId: string, runId: string) => `${API_BASE}/orgs/${orgId}/schedule_runs/${runId}`,
|
|
167
|
+
|
|
160
168
|
// Invitations (org-scoped, admin)
|
|
161
169
|
ORG_INVITATIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/invitations`,
|
|
162
170
|
ORG_INVITATION: (orgId: string, invId: string) =>
|
|
@@ -180,6 +188,7 @@ export const ENDPOINTS = {
|
|
|
180
188
|
WIKI_PAGE_INDEX: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/page-index`,
|
|
181
189
|
WIKI_REFS: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/refs`,
|
|
182
190
|
WIKI_REFS_CHECK: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/refs/check`,
|
|
191
|
+
WIKI_ANCHOR_STATUS: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/anchor-status`,
|
|
183
192
|
WIKI_CHANGESETS: (orgId: string, wikiId: string) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets`,
|
|
184
193
|
WIKI_CHANGESET: (orgId: string, wikiId: string, changesetId: string) =>
|
|
185
194
|
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}`,
|
|
@@ -189,6 +198,16 @@ export const ENDPOINTS = {
|
|
|
189
198
|
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}/merge`,
|
|
190
199
|
WIKI_CHANGESET_CLOSE: (orgId: string, wikiId: string, changesetId: string) =>
|
|
191
200
|
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}/close`,
|
|
201
|
+
WIKI_CHANGESET_FILES: (orgId: string, wikiId: string, changesetId: string) =>
|
|
202
|
+
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}/files`,
|
|
203
|
+
|
|
204
|
+
// Wiki Binary Upload / Delete (Phase 2 of wiki-file-storage-design)
|
|
205
|
+
WIKI_UPLOADS: (orgId: string, wikiId: string) =>
|
|
206
|
+
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/uploads`,
|
|
207
|
+
WIKI_FILES: (orgId: string, wikiId: string) =>
|
|
208
|
+
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/files`,
|
|
209
|
+
WIKI_FILE_PREVIEW_URL: (orgId: string, wikiId: string) =>
|
|
210
|
+
`${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/files/preview-url`,
|
|
192
211
|
|
|
193
212
|
// Wiki Path Scopes (AFCS ACL)
|
|
194
213
|
WIKI_PATH_SCOPES: (orgId: string, wikiId: string) =>
|
|
@@ -310,11 +329,85 @@ export const WS_EVENTS = {
|
|
|
310
329
|
INBOX_BULK_UPDATE: 'inbox.bulk_update',
|
|
311
330
|
READ_POSITION_UPDATED: 'read_position.updated',
|
|
312
331
|
DISPATCH_NEW: 'dispatch.new',
|
|
332
|
+
SCHEDULE_CREATED: 'schedule.created',
|
|
333
|
+
SCHEDULE_UPDATED: 'schedule.updated',
|
|
334
|
+
SCHEDULE_DELETED: 'schedule.deleted',
|
|
335
|
+
SCHEDULE_FIRED: 'schedule.fired',
|
|
313
336
|
} as const;
|
|
314
337
|
|
|
315
338
|
// Canonical target URI builders for unified comments.
|
|
339
|
+
//
|
|
340
|
+
// Heading and line anchors for wiki inline comments MUST be pinned to a full
|
|
341
|
+
// commit SHA (40-hex-char). The builders do not validate — callers are
|
|
342
|
+
// expected to pass the current wiki HEAD at comment-creation time.
|
|
343
|
+
|
|
344
|
+
// encodeHeadingSegment mirrors Go's url.PathEscape + ':' → '%3A' exactly so
|
|
345
|
+
// the Go and TS SDKs produce byte-identical target_uri values for the same
|
|
346
|
+
// heading text. encodeURIComponent differs from PathEscape on a handful of
|
|
347
|
+
// characters — this function normalizes back to PathEscape's character set:
|
|
348
|
+
// - unescape & = @ $ + (PathEscape leaves these alone, URIComponent encodes)
|
|
349
|
+
// - escape ! * ' ( ) (URIComponent leaves these alone, PathEscape encodes)
|
|
350
|
+
// ':' is already encoded as %3A by encodeURIComponent.
|
|
351
|
+
function encodeHeadingSegment(seg: string): string {
|
|
352
|
+
return encodeURIComponent(seg)
|
|
353
|
+
.replace(/%26/g, '&')
|
|
354
|
+
.replace(/%3D/g, '=')
|
|
355
|
+
.replace(/%40/g, '@')
|
|
356
|
+
.replace(/%24/g, '$')
|
|
357
|
+
.replace(/%2B/g, '+')
|
|
358
|
+
.replace(/!/g, '%21')
|
|
359
|
+
.replace(/\*/g, '%2A')
|
|
360
|
+
.replace(/'/g, '%27')
|
|
361
|
+
.replace(/\(/g, '%28')
|
|
362
|
+
.replace(/\)/g, '%29');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// encodePathSegment mirrors Go's url.URL path encoding ("encodePath" mode) for
|
|
366
|
+
// one segment (characters between '/'). Go leaves alphanumerics, -_.~, and the
|
|
367
|
+
// specific sub-delims $ & + , : ; = @ alone; it percent-encodes everything else
|
|
368
|
+
// — importantly including ! ' ( ) * which encodeURIComponent passes through.
|
|
369
|
+
// Keeping these in sync is required so wikiPage / wikiHeading / wikiLineRange
|
|
370
|
+
// produce byte-identical target_uri across Go and TS SDKs for any legal
|
|
371
|
+
// filename (including ones with ', (, ), !, *, space, #, ?, %, etc.).
|
|
372
|
+
function encodePathSegment(seg: string): string {
|
|
373
|
+
return encodeURIComponent(seg)
|
|
374
|
+
// Un-escape what Go's encodePath passes through
|
|
375
|
+
.replace(/%24/g, '$')
|
|
376
|
+
.replace(/%26/g, '&')
|
|
377
|
+
.replace(/%2B/g, '+')
|
|
378
|
+
.replace(/%2C/g, ',')
|
|
379
|
+
.replace(/%3A/g, ':')
|
|
380
|
+
.replace(/%3B/g, ';')
|
|
381
|
+
.replace(/%3D/g, '=')
|
|
382
|
+
.replace(/%40/g, '@')
|
|
383
|
+
// Encode what Go escapes but encodeURIComponent leaves raw
|
|
384
|
+
.replace(/!/g, '%21')
|
|
385
|
+
.replace(/'/g, '%27')
|
|
386
|
+
.replace(/\(/g, '%28')
|
|
387
|
+
.replace(/\)/g, '%29')
|
|
388
|
+
.replace(/\*/g, '%2A');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function encodeWikiPath(path: string): string {
|
|
392
|
+
return path
|
|
393
|
+
.replace(/^\//, '')
|
|
394
|
+
.split('/')
|
|
395
|
+
.map(encodePathSegment)
|
|
396
|
+
.join('/');
|
|
397
|
+
}
|
|
398
|
+
|
|
316
399
|
export const COMMENT_TARGET = {
|
|
317
400
|
task: (taskId: string) => `prll://${taskId}`,
|
|
318
401
|
changeset: (csId: string, wikiId: string) => `prll://${csId}?wiki=${wikiId}`,
|
|
319
|
-
wikiPage: (wikiId: string, path: string) => `prll://${wikiId}/${path
|
|
402
|
+
wikiPage: (wikiId: string, path: string) => `prll://${wikiId}/${encodeWikiPath(path)}`,
|
|
403
|
+
// Hierarchy encoded as "::"-joined PathEscape segments with ':' → '%3A' so a
|
|
404
|
+
// literal ':' in a heading cannot be confused with the separator.
|
|
405
|
+
wikiHeading: (wikiId: string, path: string, rev: string, headingSegments: string[]) => {
|
|
406
|
+
const encoded = headingSegments.map(encodeHeadingSegment).join('::');
|
|
407
|
+
return `prll://${wikiId}/${encodeWikiPath(path)}?rev=${rev}#h=${encoded}`;
|
|
408
|
+
},
|
|
409
|
+
wikiLineRange: (wikiId: string, path: string, rev: string, lineStart: number, lineEnd: number) => {
|
|
410
|
+
const frag = lineStart === lineEnd ? `l=${lineStart}` : `l=${lineStart}-${lineEnd}`;
|
|
411
|
+
return `prll://${wikiId}/${encodeWikiPath(path)}?rev=${rev}#${frag}`;
|
|
412
|
+
},
|
|
320
413
|
} as const;
|