@sideboard-ai/core 0.1.274 → 0.1.275
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/{abletime-4ZSDI4YP.js → abletime-VCVPSLSU.js} +4 -4
- package/dist/{abletime-JNQAHLHH.js → abletime-VRATUMDO.js} +4 -4
- package/dist/{abletime-oauth-4RXZH2KU.js → abletime-oauth-4C4G2J3R.js} +3 -3
- package/dist/{abletime-oauth-DCEIINYT.js → abletime-oauth-4K7MICAD.js} +3 -3
- package/dist/{abletime-rest-KVBJFVFD.js → abletime-rest-6N63ZVQ3.js} +3 -3
- package/dist/{abletime-rest-VN6PZ6P2.js → abletime-rest-PIBCNHF3.js} +3 -3
- package/dist/{agents-LAFFXBBZ.js → agents-2FOWVJCN.js} +4 -4
- package/dist/{agents-ZCO37SKG.js → agents-JBVJRDH3.js} +4 -4
- package/dist/{app-settings-RAC4PSSP.js → app-settings-EQQZQNYD.js} +1 -1
- package/dist/{app-settings-L5HGYXWJ.js → app-settings-NI4X5HW3.js} +1 -1
- package/dist/{chunk-UXI52MLJ.js → chunk-2J34G3WJ.js} +3 -3
- package/dist/{chunk-PTNGMA5C.js → chunk-A2G3ZBJS.js} +2 -2
- package/dist/{chunk-BJ5T22L5.js → chunk-FELFTNH2.js} +22 -21
- package/dist/{chunk-GCSG3OGV.js → chunk-HOMZKYHF.js} +29 -27
- package/dist/{chunk-GONPMH36.js → chunk-IJDQKC5A.js} +5 -5
- package/dist/{chunk-USBZ7PH4.js → chunk-IPPY3FG5.js} +1 -1
- package/dist/{chunk-NWE3NOWQ.js → chunk-K7MWJLR7.js} +1 -1
- package/dist/{chunk-DVTEC4BF.js → chunk-MGS5T2A3.js} +29 -27
- package/dist/{chunk-ADWNK2JH.js → chunk-NIB2IZ6J.js} +5 -5
- package/dist/{chunk-AGXZAMOF.js → chunk-NQ7JMVSU.js} +2 -2
- package/dist/{chunk-JLDJGGCL.js → chunk-NT7RIWRR.js} +5 -5
- package/dist/{chunk-L6CYOON4.js → chunk-PZURG3XN.js} +5 -5
- package/dist/{chunk-LAIBYKZS.js → chunk-QINJKNLR.js} +3 -3
- package/dist/{chunk-2QUILREI.js → chunk-QTZO4LPU.js} +2 -2
- package/dist/{chunk-KZ2CDZBZ.js → chunk-R7HLXD4I.js} +3 -3
- package/dist/{chunk-XIVM7K77.js → chunk-SQOAFR2U.js} +23 -22
- package/dist/{chunk-OIENQEGG.js → chunk-WRIMAEF2.js} +67 -122
- package/dist/{chunk-DBFAFI5A.js → chunk-XKZAVGTT.js} +2 -2
- package/dist/{chunk-PAH7KDL2.js → chunk-YNTXWFXT.js} +67 -122
- package/dist/{conductor-B2BXSA7N.js → conductor-GBCE37K4.js} +4 -4
- package/dist/{conductor-DXBLL23V.js → conductor-YYQKZAND.js} +4 -4
- package/dist/{coordinator-prompt-4KVA6XUX.js → coordinator-prompt-B3GLVXPP.js} +3 -3
- package/dist/{coordinator-prompt-MVUHFNSC.js → coordinator-prompt-SFZCZ3HR.js} +3 -3
- package/dist/{global-workspace-FR7QTLPK.js → global-workspace-7XEXTIHL.js} +3 -3
- package/dist/{global-workspace-34Z2IWYQ.js → global-workspace-N2YYJKA5.js} +3 -3
- package/dist/index.cjs +1695 -181
- package/dist/index.d.cts +99 -20
- package/dist/index.d.ts +99 -20
- package/dist/index.js +1583 -43
- package/dist/mcp/run-stdio.cjs +90 -136
- package/dist/mcp/run-stdio.js +11 -11
- package/dist/{orchestrator-6T35CJHB.js → orchestrator-6QJLM6Y4.js} +6 -6
- package/dist/{orchestrator-QRSEOHXI.js → orchestrator-QMKVXVUR.js} +6 -6
- package/dist/{workspaces-5GERYHDJ.js → workspaces-BJCHMGVG.js} +3 -3
- package/dist/{workspaces-2JBDUMM6.js → workspaces-PBB6HY2D.js} +3 -3
- package/dist/{worktree-ACW23KJ5.js → worktree-L57T3KH7.js} +2 -2
- package/dist/{worktree-C7RPVHZ7.js → worktree-Q6UJHG25.js} +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1081,6 +1081,9 @@ function normalizeVault(raw) {
|
|
|
1081
1081
|
if (typeof raw.remoteHostSecret === "string" && raw.remoteHostSecret.trim()) {
|
|
1082
1082
|
out.remoteHostSecret = raw.remoteHostSecret.trim();
|
|
1083
1083
|
}
|
|
1084
|
+
if (typeof raw.remoteAccountToken === "string" && raw.remoteAccountToken.trim()) {
|
|
1085
|
+
out.remoteAccountToken = raw.remoteAccountToken.trim();
|
|
1086
|
+
}
|
|
1084
1087
|
if (typeof raw.githubPat === "string" && raw.githubPat.trim()) {
|
|
1085
1088
|
out.githubPat = raw.githubPat.trim();
|
|
1086
1089
|
}
|
|
@@ -1777,7 +1780,8 @@ function emptyPublicIntegrations() {
|
|
|
1777
1780
|
hasVercelToken: false,
|
|
1778
1781
|
hasSupabaseToken: false,
|
|
1779
1782
|
hasPosthogToken: false,
|
|
1780
|
-
hasSentryToken: false
|
|
1783
|
+
hasSentryToken: false,
|
|
1784
|
+
hasRemoteAccount: false
|
|
1781
1785
|
};
|
|
1782
1786
|
}
|
|
1783
1787
|
function hasLinearOAuth(integrations) {
|
|
@@ -1794,29 +1798,7 @@ function toPublicAppSettings(settings) {
|
|
|
1794
1798
|
if (key.trim()) environment[key] = "";
|
|
1795
1799
|
}
|
|
1796
1800
|
const integrations = { ...settings.integrations };
|
|
1797
|
-
const
|
|
1798
|
-
const slackAppToken = integrations.slackAppToken;
|
|
1799
|
-
const githubPat = integrations.githubPat;
|
|
1800
|
-
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
1801
|
-
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
1802
|
-
const vercelToken = integrations.vercelToken;
|
|
1803
|
-
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
1804
|
-
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
1805
|
-
const sentryAuthToken = integrations.sentryAuthToken;
|
|
1806
|
-
delete integrations.linearApiKey;
|
|
1807
|
-
delete integrations.linearAccessToken;
|
|
1808
|
-
delete integrations.linearRefreshToken;
|
|
1809
|
-
delete integrations.linearClientSecret;
|
|
1810
|
-
delete integrations.slackClientSecret;
|
|
1811
|
-
delete integrations.slackAppToken;
|
|
1812
|
-
delete integrations.remoteHostSecret;
|
|
1813
|
-
delete integrations.githubPat;
|
|
1814
|
-
delete integrations.abletimeAccessToken;
|
|
1815
|
-
delete integrations.abletimeRefreshToken;
|
|
1816
|
-
delete integrations.vercelToken;
|
|
1817
|
-
delete integrations.supabaseAccessToken;
|
|
1818
|
-
delete integrations.posthogPersonalApiKey;
|
|
1819
|
-
delete integrations.sentryAuthToken;
|
|
1801
|
+
for (const key of INTEGRATION_VAULT_KEYS) delete integrations[key];
|
|
1820
1802
|
return {
|
|
1821
1803
|
...settings,
|
|
1822
1804
|
environment,
|
|
@@ -1824,15 +1806,18 @@ function toPublicAppSettings(settings) {
|
|
|
1824
1806
|
...integrations,
|
|
1825
1807
|
hasLinearApiKey: hasLinearCredentials(settings.integrations),
|
|
1826
1808
|
hasLinearOAuth: hasLinearOAuth(settings.integrations),
|
|
1827
|
-
hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
|
|
1828
|
-
hasSlackAppToken: Boolean(slackAppToken?.trim()),
|
|
1829
|
-
hasGithubPat: Boolean(githubPat?.trim()),
|
|
1830
|
-
hasAbleTimeToken: Boolean(
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1809
|
+
hasSlackClientSecret: Boolean(settings.integrations.slackClientSecret?.trim()),
|
|
1810
|
+
hasSlackAppToken: Boolean(settings.integrations.slackAppToken?.trim()),
|
|
1811
|
+
hasGithubPat: Boolean(settings.integrations.githubPat?.trim()),
|
|
1812
|
+
hasAbleTimeToken: Boolean(
|
|
1813
|
+
settings.integrations.abletimeAccessToken?.trim() || settings.integrations.abletimeRefreshToken?.trim()
|
|
1814
|
+
),
|
|
1815
|
+
hasAbleTimeOAuth: Boolean(settings.integrations.abletimeRefreshToken?.trim()),
|
|
1816
|
+
hasVercelToken: Boolean(settings.integrations.vercelToken?.trim()),
|
|
1817
|
+
hasSupabaseToken: Boolean(settings.integrations.supabaseAccessToken?.trim()),
|
|
1818
|
+
hasPosthogToken: Boolean(settings.integrations.posthogPersonalApiKey?.trim()),
|
|
1819
|
+
hasSentryToken: Boolean(settings.integrations.sentryAuthToken?.trim()),
|
|
1820
|
+
hasRemoteAccount: Boolean(settings.integrations.remoteAccountToken?.trim())
|
|
1836
1821
|
}
|
|
1837
1822
|
};
|
|
1838
1823
|
}
|
|
@@ -1959,6 +1944,14 @@ function normalizeIntegrations(raw) {
|
|
|
1959
1944
|
const secret = source.remoteHostSecret.trim();
|
|
1960
1945
|
if (secret) out.remoteHostSecret = secret;
|
|
1961
1946
|
}
|
|
1947
|
+
if (typeof source.remoteAccountToken === "string") {
|
|
1948
|
+
const token = source.remoteAccountToken.trim();
|
|
1949
|
+
if (token) out.remoteAccountToken = token;
|
|
1950
|
+
}
|
|
1951
|
+
if (typeof source.remoteAccountEmail === "string") {
|
|
1952
|
+
const email = source.remoteAccountEmail.trim().toLowerCase();
|
|
1953
|
+
if (email) out.remoteAccountEmail = email;
|
|
1954
|
+
}
|
|
1962
1955
|
if (typeof source.githubGitAuthMode === "string" && GIT_AUTH_MODES.has(source.githubGitAuthMode)) {
|
|
1963
1956
|
out.githubGitAuthMode = source.githubGitAuthMode;
|
|
1964
1957
|
}
|
|
@@ -2302,110 +2295,31 @@ function readSettingsFile() {
|
|
|
2302
2295
|
}
|
|
2303
2296
|
}
|
|
2304
2297
|
function diskHoldsSecrets(disk) {
|
|
2305
|
-
return Boolean(
|
|
2306
|
-
disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.remoteHostSecret || disk.integrations.githubPat || disk.integrations.abletimeAccessToken || disk.integrations.abletimeRefreshToken || disk.integrations.vercelToken || disk.integrations.supabaseAccessToken || disk.integrations.posthogPersonalApiKey || disk.integrations.sentryAuthToken || Object.keys(disk.environment).length > 0
|
|
2307
|
-
);
|
|
2298
|
+
return INTEGRATION_VAULT_KEYS.some((key) => Boolean(disk.integrations[key])) || Object.keys(disk.environment).length > 0;
|
|
2308
2299
|
}
|
|
2309
2300
|
function mergeVault(disk) {
|
|
2310
2301
|
const vault = loadSecretVault();
|
|
2311
2302
|
const environment = { ...vault.environment ?? {}, ...disk.environment };
|
|
2312
2303
|
const integrations = { ...disk.integrations };
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
if (vault.linearAccessToken && !integrations.linearAccessToken) {
|
|
2317
|
-
integrations.linearAccessToken = vault.linearAccessToken;
|
|
2318
|
-
}
|
|
2319
|
-
if (vault.linearRefreshToken && !integrations.linearRefreshToken) {
|
|
2320
|
-
integrations.linearRefreshToken = vault.linearRefreshToken;
|
|
2321
|
-
}
|
|
2322
|
-
if (vault.linearClientSecret && !integrations.linearClientSecret) {
|
|
2323
|
-
integrations.linearClientSecret = vault.linearClientSecret;
|
|
2324
|
-
}
|
|
2325
|
-
if (vault.slackClientSecret && !integrations.slackClientSecret) {
|
|
2326
|
-
integrations.slackClientSecret = vault.slackClientSecret;
|
|
2327
|
-
}
|
|
2328
|
-
if (vault.slackAppToken && !integrations.slackAppToken) {
|
|
2329
|
-
integrations.slackAppToken = vault.slackAppToken;
|
|
2330
|
-
}
|
|
2331
|
-
if (vault.remoteHostSecret && !integrations.remoteHostSecret) {
|
|
2332
|
-
integrations.remoteHostSecret = vault.remoteHostSecret;
|
|
2333
|
-
}
|
|
2334
|
-
if (vault.githubPat && !integrations.githubPat) {
|
|
2335
|
-
integrations.githubPat = vault.githubPat;
|
|
2336
|
-
}
|
|
2337
|
-
if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
|
|
2338
|
-
integrations.abletimeAccessToken = vault.abletimeAccessToken;
|
|
2339
|
-
}
|
|
2340
|
-
if (vault.abletimeRefreshToken && !integrations.abletimeRefreshToken) {
|
|
2341
|
-
integrations.abletimeRefreshToken = vault.abletimeRefreshToken;
|
|
2342
|
-
}
|
|
2343
|
-
if (vault.vercelToken && !integrations.vercelToken) {
|
|
2344
|
-
integrations.vercelToken = vault.vercelToken;
|
|
2345
|
-
}
|
|
2346
|
-
if (vault.supabaseAccessToken && !integrations.supabaseAccessToken) {
|
|
2347
|
-
integrations.supabaseAccessToken = vault.supabaseAccessToken;
|
|
2348
|
-
}
|
|
2349
|
-
if (vault.posthogPersonalApiKey && !integrations.posthogPersonalApiKey) {
|
|
2350
|
-
integrations.posthogPersonalApiKey = vault.posthogPersonalApiKey;
|
|
2351
|
-
}
|
|
2352
|
-
if (vault.sentryAuthToken && !integrations.sentryAuthToken) {
|
|
2353
|
-
integrations.sentryAuthToken = vault.sentryAuthToken;
|
|
2304
|
+
for (const key of INTEGRATION_VAULT_KEYS) {
|
|
2305
|
+
const stored = vault[key];
|
|
2306
|
+
if (stored && !integrations[key]) integrations[key] = stored;
|
|
2354
2307
|
}
|
|
2355
2308
|
return { ...disk, environment, integrations };
|
|
2356
2309
|
}
|
|
2357
2310
|
function persistSplit(settings) {
|
|
2358
2311
|
const integrations = { ...settings.integrations };
|
|
2359
|
-
const
|
|
2360
|
-
const
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
const slackAppToken = integrations.slackAppToken;
|
|
2365
|
-
const remoteHostSecret = integrations.remoteHostSecret;
|
|
2366
|
-
const githubPat = integrations.githubPat;
|
|
2367
|
-
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
2368
|
-
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
2369
|
-
const vercelToken = integrations.vercelToken;
|
|
2370
|
-
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
2371
|
-
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
2372
|
-
const sentryAuthToken = integrations.sentryAuthToken;
|
|
2373
|
-
delete integrations.linearApiKey;
|
|
2374
|
-
delete integrations.linearAccessToken;
|
|
2375
|
-
delete integrations.linearRefreshToken;
|
|
2376
|
-
delete integrations.linearClientSecret;
|
|
2377
|
-
delete integrations.slackClientSecret;
|
|
2378
|
-
delete integrations.slackAppToken;
|
|
2379
|
-
delete integrations.remoteHostSecret;
|
|
2380
|
-
delete integrations.githubPat;
|
|
2381
|
-
delete integrations.abletimeAccessToken;
|
|
2382
|
-
delete integrations.abletimeRefreshToken;
|
|
2383
|
-
delete integrations.vercelToken;
|
|
2384
|
-
delete integrations.supabaseAccessToken;
|
|
2385
|
-
delete integrations.posthogPersonalApiKey;
|
|
2386
|
-
delete integrations.sentryAuthToken;
|
|
2312
|
+
const vault = { environment: settings.environment };
|
|
2313
|
+
for (const key of INTEGRATION_VAULT_KEYS) {
|
|
2314
|
+
vault[key] = integrations[key];
|
|
2315
|
+
delete integrations[key];
|
|
2316
|
+
}
|
|
2387
2317
|
writePrivateFile(
|
|
2388
2318
|
appSettingsPath(),
|
|
2389
2319
|
`${JSON.stringify({ ...settings, environment: {}, integrations }, null, 2)}
|
|
2390
2320
|
`
|
|
2391
2321
|
);
|
|
2392
|
-
saveSecretVault(
|
|
2393
|
-
linearApiKey,
|
|
2394
|
-
linearAccessToken,
|
|
2395
|
-
linearRefreshToken,
|
|
2396
|
-
linearClientSecret,
|
|
2397
|
-
slackClientSecret,
|
|
2398
|
-
slackAppToken,
|
|
2399
|
-
remoteHostSecret,
|
|
2400
|
-
githubPat,
|
|
2401
|
-
abletimeAccessToken,
|
|
2402
|
-
abletimeRefreshToken,
|
|
2403
|
-
vercelToken,
|
|
2404
|
-
supabaseAccessToken,
|
|
2405
|
-
posthogPersonalApiKey,
|
|
2406
|
-
sentryAuthToken,
|
|
2407
|
-
environment: settings.environment
|
|
2408
|
-
});
|
|
2322
|
+
saveSecretVault(vault);
|
|
2409
2323
|
}
|
|
2410
2324
|
function loadAppSettings() {
|
|
2411
2325
|
const disk = readSettingsFile();
|
|
@@ -2616,6 +2530,20 @@ function updateIntegrationsSettings(patch) {
|
|
|
2616
2530
|
integrations.remoteHostSecret = patch.remoteHostSecret.trim();
|
|
2617
2531
|
}
|
|
2618
2532
|
}
|
|
2533
|
+
if ("remoteAccountToken" in patch) {
|
|
2534
|
+
if (patch.remoteAccountToken == null || patch.remoteAccountToken.trim() === "") {
|
|
2535
|
+
delete integrations.remoteAccountToken;
|
|
2536
|
+
} else {
|
|
2537
|
+
integrations.remoteAccountToken = patch.remoteAccountToken.trim();
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
if ("remoteAccountEmail" in patch) {
|
|
2541
|
+
if (patch.remoteAccountEmail == null || patch.remoteAccountEmail.trim() === "") {
|
|
2542
|
+
delete integrations.remoteAccountEmail;
|
|
2543
|
+
} else {
|
|
2544
|
+
integrations.remoteAccountEmail = patch.remoteAccountEmail.trim().toLowerCase();
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2619
2547
|
if ("githubGitAuthMode" in patch) {
|
|
2620
2548
|
if (patch.githubGitAuthMode == null) {
|
|
2621
2549
|
delete integrations.githubGitAuthMode;
|
|
@@ -3354,7 +3282,7 @@ function childEnvWithAppSettings(extra) {
|
|
|
3354
3282
|
function harnessEnvKey(harness) {
|
|
3355
3283
|
return HARNESS_ENV_KEYS[harness];
|
|
3356
3284
|
}
|
|
3357
|
-
var import_node_crypto2, import_node_fs9, import_node_os4, import_node_path10, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, AGENT_DONE_SOUNDS, CHAT_TEXT_SCALES, CHAT_TEXT_SCALE_DEFAULT, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, REVIEW_LABEL_MAX, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
|
|
3285
|
+
var import_node_crypto2, import_node_fs9, import_node_os4, import_node_path10, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, AGENT_DONE_SOUNDS, CHAT_TEXT_SCALES, CHAT_TEXT_SCALE_DEFAULT, INTEGRATION_VAULT_KEYS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, REVIEW_LABEL_MAX, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
|
|
3358
3286
|
var init_app_settings = __esm({
|
|
3359
3287
|
"src/store/app-settings.ts"() {
|
|
3360
3288
|
"use strict";
|
|
@@ -3407,6 +3335,23 @@ var init_app_settings = __esm({
|
|
|
3407
3335
|
];
|
|
3408
3336
|
CHAT_TEXT_SCALES = [1.2, 1.4];
|
|
3409
3337
|
CHAT_TEXT_SCALE_DEFAULT = 1.2;
|
|
3338
|
+
INTEGRATION_VAULT_KEYS = [
|
|
3339
|
+
"linearApiKey",
|
|
3340
|
+
"linearAccessToken",
|
|
3341
|
+
"linearRefreshToken",
|
|
3342
|
+
"linearClientSecret",
|
|
3343
|
+
"slackClientSecret",
|
|
3344
|
+
"slackAppToken",
|
|
3345
|
+
"remoteHostSecret",
|
|
3346
|
+
"remoteAccountToken",
|
|
3347
|
+
"githubPat",
|
|
3348
|
+
"abletimeAccessToken",
|
|
3349
|
+
"abletimeRefreshToken",
|
|
3350
|
+
"vercelToken",
|
|
3351
|
+
"supabaseAccessToken",
|
|
3352
|
+
"posthogPersonalApiKey",
|
|
3353
|
+
"sentryAuthToken"
|
|
3354
|
+
];
|
|
3410
3355
|
CLOUD_CONNECT_AGENTS = /* @__PURE__ */ new Set([
|
|
3411
3356
|
"claude",
|
|
3412
3357
|
"codex",
|
|
@@ -18515,7 +18460,7 @@ function lastRequestOccupancy(thread) {
|
|
|
18515
18460
|
function shouldResetSessionForOccupancy(thread, occupancyTokens = SESSION_RESET_OCCUPANCY_TOKENS) {
|
|
18516
18461
|
return lastRequestOccupancy(thread) >= occupancyTokens;
|
|
18517
18462
|
}
|
|
18518
|
-
async function maybeCompactContext(thread, thresholds = {},
|
|
18463
|
+
async function maybeCompactContext(thread, thresholds = {}, summarize2 = summarizeConversation) {
|
|
18519
18464
|
if (!shouldCompactContext(thread.messages, thresholds)) {
|
|
18520
18465
|
return { didCompact: false, thread };
|
|
18521
18466
|
}
|
|
@@ -18524,7 +18469,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
18524
18469
|
return { didCompact: false, thread };
|
|
18525
18470
|
}
|
|
18526
18471
|
const transcript = formatMessagesAsTranscript(older, { tools: "summary" });
|
|
18527
|
-
const { summary, method } = await
|
|
18472
|
+
const { summary, method } = await summarize2(transcript, {
|
|
18528
18473
|
cwd: thread.worktreePath
|
|
18529
18474
|
});
|
|
18530
18475
|
let messages = applyCompaction(thread.messages, summary, thresholds);
|
|
@@ -19213,17 +19158,7 @@ var init_worktree_run_ports = __esm({
|
|
|
19213
19158
|
}
|
|
19214
19159
|
});
|
|
19215
19160
|
|
|
19216
|
-
// src/mcp/
|
|
19217
|
-
function mcpWaitForJobTimeoutMs(requested) {
|
|
19218
|
-
return mcpWaitForTurnTimeoutMs(requested);
|
|
19219
|
-
}
|
|
19220
|
-
function sanitizeDetachedJobId(id) {
|
|
19221
|
-
const s = id.trim();
|
|
19222
|
-
if (!JOB_ID_RE.test(s)) {
|
|
19223
|
-
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
19224
|
-
}
|
|
19225
|
-
return s;
|
|
19226
|
-
}
|
|
19161
|
+
// src/mcp/job-process.ts
|
|
19227
19162
|
function jobAlive(pid) {
|
|
19228
19163
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
19229
19164
|
try {
|
|
@@ -19247,6 +19182,23 @@ function jobTreeAlive(pid) {
|
|
|
19247
19182
|
if (pid == null) return false;
|
|
19248
19183
|
return jobAlive(pid) || processGroupAlive(pid);
|
|
19249
19184
|
}
|
|
19185
|
+
var init_job_process = __esm({
|
|
19186
|
+
"src/mcp/job-process.ts"() {
|
|
19187
|
+
"use strict";
|
|
19188
|
+
}
|
|
19189
|
+
});
|
|
19190
|
+
|
|
19191
|
+
// src/mcp/wait-for-job.ts
|
|
19192
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
19193
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
19194
|
+
}
|
|
19195
|
+
function sanitizeDetachedJobId(id) {
|
|
19196
|
+
const s = id.trim();
|
|
19197
|
+
if (!JOB_ID_RE.test(s)) {
|
|
19198
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
19199
|
+
}
|
|
19200
|
+
return s;
|
|
19201
|
+
}
|
|
19250
19202
|
function readIntFile(file) {
|
|
19251
19203
|
if (!(0, import_node_fs50.existsSync)(file)) return null;
|
|
19252
19204
|
const n = Number.parseInt((0, import_node_fs50.readFileSync)(file, "utf8").trim(), 10);
|
|
@@ -19430,7 +19382,7 @@ async function waitForDetachedJob(cwd, id, opts) {
|
|
|
19430
19382
|
const root = cwd.trim() || process.cwd();
|
|
19431
19383
|
const dir = resolveJobDir(root, jobId);
|
|
19432
19384
|
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
19433
|
-
const
|
|
19385
|
+
const sleep3 = opts?.sleep ?? sleepMs;
|
|
19434
19386
|
if (!(0, import_node_fs50.existsSync)(dir)) {
|
|
19435
19387
|
return {
|
|
19436
19388
|
stillRunning: false,
|
|
@@ -19456,7 +19408,7 @@ async function waitForDetachedJob(cwd, id, opts) {
|
|
|
19456
19408
|
snap = snapshotJob(dir);
|
|
19457
19409
|
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
19458
19410
|
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
19459
|
-
await
|
|
19411
|
+
await sleep3(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
19460
19412
|
}
|
|
19461
19413
|
snap = snapshotJob(dir);
|
|
19462
19414
|
return toResult(jobId, snap);
|
|
@@ -19488,7 +19440,7 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
19488
19440
|
const jobId = sanitizeDetachedJobId(id);
|
|
19489
19441
|
const root = cwd.trim() || process.cwd();
|
|
19490
19442
|
const dir = resolveJobDir(root, jobId);
|
|
19491
|
-
const
|
|
19443
|
+
const sleep3 = opts?.sleep ?? sleepMs;
|
|
19492
19444
|
const why = opts?.reason?.trim() ?? "";
|
|
19493
19445
|
if (!(0, import_node_fs50.existsSync)(dir)) {
|
|
19494
19446
|
return {
|
|
@@ -19520,11 +19472,11 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
19520
19472
|
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
19521
19473
|
const deadline = Date.now() + graceMs;
|
|
19522
19474
|
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
19523
|
-
await
|
|
19475
|
+
await sleep3(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
19524
19476
|
}
|
|
19525
19477
|
if (targetPid != null) {
|
|
19526
19478
|
killProcessTree(targetPid, "SIGKILL");
|
|
19527
|
-
await
|
|
19479
|
+
await sleep3(100);
|
|
19528
19480
|
}
|
|
19529
19481
|
const exitFile = (0, import_node_path50.join)(dir, "exit");
|
|
19530
19482
|
if (!(0, import_node_fs50.existsSync)(exitFile)) {
|
|
@@ -19556,6 +19508,7 @@ var init_wait_for_job = __esm({
|
|
|
19556
19508
|
import_node_fs50 = require("fs");
|
|
19557
19509
|
import_node_path50 = require("path");
|
|
19558
19510
|
init_workspace_scratch();
|
|
19511
|
+
init_job_process();
|
|
19559
19512
|
init_wait_for_turn();
|
|
19560
19513
|
MAX_JOB_CONTINUES = 8;
|
|
19561
19514
|
MAX_JOB_DELTA_LINES = 80;
|
|
@@ -26890,7 +26843,8 @@ var init_orchestrator = __esm({
|
|
|
26890
26843
|
throw new Error(`Thread is archived: ${thread.id}`);
|
|
26891
26844
|
}
|
|
26892
26845
|
const followUp = resolveSendFollowUp(current, opts?.followUp);
|
|
26893
|
-
const
|
|
26846
|
+
const explicit = opts?.attachments;
|
|
26847
|
+
const consumed = explicit !== void 0 ? { attachments: current.attachments, consumed: explicit } : consumeComposerAttachments(current);
|
|
26894
26848
|
const queued = appendQueuedItem(
|
|
26895
26849
|
current.queue,
|
|
26896
26850
|
current.queueAttachments,
|
|
@@ -26904,7 +26858,7 @@ var init_orchestrator = __esm({
|
|
|
26904
26858
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
|
|
26905
26859
|
shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
|
|
26906
26860
|
const patch = queued;
|
|
26907
|
-
if (consumed.consumed.length > 0) {
|
|
26861
|
+
if (explicit === void 0 && consumed.consumed.length > 0) {
|
|
26908
26862
|
patch.attachments = consumed.attachments;
|
|
26909
26863
|
}
|
|
26910
26864
|
if (!inFlight) patch.status = "queued";
|
|
@@ -29393,6 +29347,7 @@ __export(index_exports, {
|
|
|
29393
29347
|
disconnectLinear: () => disconnectLinear,
|
|
29394
29348
|
disconnectLinearConnection: () => disconnectLinearConnection,
|
|
29395
29349
|
disconnectOptionalService: () => disconnectOptionalService,
|
|
29350
|
+
disconnectRemoteAccount: () => disconnectRemoteAccount,
|
|
29396
29351
|
disconnectSlackWorkspace: () => disconnectSlackWorkspace,
|
|
29397
29352
|
discoverSkills: () => discoverSkills,
|
|
29398
29353
|
downloadAbleTimeAttachment: () => downloadAbleTimeAttachment,
|
|
@@ -29649,6 +29604,7 @@ __export(index_exports, {
|
|
|
29649
29604
|
isPrNotMergeableError: () => isPrNotMergeableError,
|
|
29650
29605
|
isPresentPlanToolName: () => isPresentPlanToolName,
|
|
29651
29606
|
isPrimaryCheckoutThread: () => isPrimaryCheckoutThread,
|
|
29607
|
+
isRemoteAccountLoginCancelled: () => isRemoteAccountLoginCancelled,
|
|
29652
29608
|
isRemoteStopCommand: () => isRemoteStopCommand,
|
|
29653
29609
|
isSelfWrittenRecord: () => isSelfWrittenRecord,
|
|
29654
29610
|
isSessionQuotaLimit: () => isSessionQuotaLimit,
|
|
@@ -30010,6 +29966,7 @@ __export(index_exports, {
|
|
|
30010
29966
|
startMcpServer: () => startMcpServer,
|
|
30011
29967
|
startOrchestration: () => startOrchestration,
|
|
30012
29968
|
startRelayServer: () => startRelayServer,
|
|
29969
|
+
startRemoteAccountLogin: () => startRemoteAccountLogin,
|
|
30013
29970
|
startSlackOAuth: () => startSlackOAuth,
|
|
30014
29971
|
statWorktreePath: () => statWorktreePath,
|
|
30015
29972
|
stopAgentRpcServer: () => stopAgentRpcServer,
|
|
@@ -30543,12 +30500,12 @@ async function persistLinearTokens(data, viewer) {
|
|
|
30543
30500
|
organizationName: viewer?.organizationName
|
|
30544
30501
|
});
|
|
30545
30502
|
}
|
|
30546
|
-
async function fetchLinearViewer(
|
|
30503
|
+
async function fetchLinearViewer(accessToken2) {
|
|
30547
30504
|
const res = await httpFetch(LINEAR_GRAPHQL, {
|
|
30548
30505
|
method: "POST",
|
|
30549
30506
|
headers: {
|
|
30550
30507
|
"Content-Type": "application/json",
|
|
30551
|
-
Authorization: linearAuthorizationHeader(
|
|
30508
|
+
Authorization: linearAuthorizationHeader(accessToken2)
|
|
30552
30509
|
},
|
|
30553
30510
|
body: JSON.stringify({
|
|
30554
30511
|
query: "{ viewer { name organization { name } } }"
|
|
@@ -38389,7 +38346,14 @@ function parseRemoteClientMessage(raw) {
|
|
|
38389
38346
|
const hostSecret = str5(parsed.hostSecret);
|
|
38390
38347
|
const deviceLabel = str5(parsed.deviceLabel);
|
|
38391
38348
|
if (!deviceId || !hostSecret || !deviceLabel) return null;
|
|
38392
|
-
|
|
38349
|
+
const accountToken = str5(parsed.accountToken);
|
|
38350
|
+
return {
|
|
38351
|
+
type: "host_register",
|
|
38352
|
+
deviceId,
|
|
38353
|
+
hostSecret,
|
|
38354
|
+
deviceLabel,
|
|
38355
|
+
...accountToken ? { accountToken } : {}
|
|
38356
|
+
};
|
|
38393
38357
|
}
|
|
38394
38358
|
if (msg.type === "pair") {
|
|
38395
38359
|
const code = str5(parsed.code).toUpperCase();
|
|
@@ -38518,6 +38482,10 @@ function newCode() {
|
|
|
38518
38482
|
return code;
|
|
38519
38483
|
}
|
|
38520
38484
|
var RemoteHub = class {
|
|
38485
|
+
constructor(opts = {}) {
|
|
38486
|
+
this.opts = opts;
|
|
38487
|
+
}
|
|
38488
|
+
opts;
|
|
38521
38489
|
hosts = /* @__PURE__ */ new Map();
|
|
38522
38490
|
remembered = /* @__PURE__ */ new Map();
|
|
38523
38491
|
bySocket = /* @__PURE__ */ new Map();
|
|
@@ -38556,7 +38524,7 @@ var RemoteHub = class {
|
|
|
38556
38524
|
}
|
|
38557
38525
|
dispatch(socket, msg) {
|
|
38558
38526
|
if (msg.type === "host_register") {
|
|
38559
|
-
this.registerHost(socket, msg.deviceId, msg.deviceLabel, msg.hostSecret);
|
|
38527
|
+
this.registerHost(socket, msg.deviceId, msg.deviceLabel, msg.hostSecret, msg.accountToken ?? "");
|
|
38560
38528
|
return;
|
|
38561
38529
|
}
|
|
38562
38530
|
if (msg.type === "host_pair") {
|
|
@@ -38583,7 +38551,14 @@ var RemoteHub = class {
|
|
|
38583
38551
|
this.fromHost(socket, msg);
|
|
38584
38552
|
}
|
|
38585
38553
|
}
|
|
38586
|
-
registerHost(socket, deviceId, deviceLabel, hostSecret) {
|
|
38554
|
+
registerHost(socket, deviceId, deviceLabel, hostSecret, accountToken) {
|
|
38555
|
+
if (this.opts.authorizeHost) {
|
|
38556
|
+
const decision = this.opts.authorizeHost({ accountToken, deviceId, deviceLabel, hostSecret });
|
|
38557
|
+
if (!decision.ok) {
|
|
38558
|
+
send2(socket, { type: "error", message: decision.message });
|
|
38559
|
+
return;
|
|
38560
|
+
}
|
|
38561
|
+
}
|
|
38587
38562
|
const prior = this.remembered.get(deviceId);
|
|
38588
38563
|
const existing = this.hosts.get(deviceId);
|
|
38589
38564
|
if (prior && prior.hostSecret !== hostSecret || existing && existing.hostSecret !== hostSecret) {
|
|
@@ -38615,6 +38590,7 @@ var RemoteHub = class {
|
|
|
38615
38590
|
this.bySocket.set(socket, { role: "host", deviceId });
|
|
38616
38591
|
send2(socket, { type: "registered", deviceId, deviceLabel });
|
|
38617
38592
|
if (host.phone) send2(socket, { type: "phone_joined" });
|
|
38593
|
+
this.opts.onAuthorized?.(socket);
|
|
38618
38594
|
}
|
|
38619
38595
|
issueCode(socket) {
|
|
38620
38596
|
const bound = this.bySocket.get(socket);
|
|
@@ -38676,6 +38652,7 @@ var RemoteHub = class {
|
|
|
38676
38652
|
online: Boolean(host)
|
|
38677
38653
|
};
|
|
38678
38654
|
});
|
|
38655
|
+
if (hosts.some((host) => host.deviceId)) this.opts.onAuthorized?.(socket);
|
|
38679
38656
|
send2(socket, { type: "hosts", hosts });
|
|
38680
38657
|
}
|
|
38681
38658
|
releasePhoneSocket(socket) {
|
|
@@ -38715,6 +38692,7 @@ var RemoteHub = class {
|
|
|
38715
38692
|
deviceLabel: host.deviceLabel
|
|
38716
38693
|
});
|
|
38717
38694
|
this.bySocket.set(socket, { role: "phone", deviceId: host.deviceId });
|
|
38695
|
+
this.opts.onAuthorized?.(socket);
|
|
38718
38696
|
send2(socket, {
|
|
38719
38697
|
type: "paired",
|
|
38720
38698
|
deviceId: host.deviceId,
|
|
@@ -38753,10 +38731,545 @@ var import_ws5 = require("ws");
|
|
|
38753
38731
|
|
|
38754
38732
|
// src/remote/listen.ts
|
|
38755
38733
|
init_orchestrator_capable();
|
|
38734
|
+
init_stage_files();
|
|
38756
38735
|
init_orchestrator();
|
|
38757
38736
|
init_app_settings();
|
|
38758
38737
|
init_global_workspace();
|
|
38759
38738
|
init_thread_store();
|
|
38739
|
+
init_implied_first_prompt();
|
|
38740
|
+
|
|
38741
|
+
// src/remote/phone-chats.ts
|
|
38742
|
+
var import_node_crypto25 = require("crypto");
|
|
38743
|
+
init_list_models();
|
|
38744
|
+
init_home_board();
|
|
38745
|
+
init_stage_files();
|
|
38746
|
+
init_worktree();
|
|
38747
|
+
init_worktree_labels();
|
|
38748
|
+
init_thinking_effort();
|
|
38749
|
+
init_app_settings();
|
|
38750
|
+
init_global_workspace();
|
|
38751
|
+
init_thread_store();
|
|
38752
|
+
init_workspaces();
|
|
38753
|
+
init_chat_tabs();
|
|
38754
|
+
init_create();
|
|
38755
|
+
init_orchestrator();
|
|
38756
|
+
var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
|
|
38757
|
+
var PHONE_USER_PREFIX = "Phone\n\n";
|
|
38758
|
+
var TRANSCRIPT_LIMIT = 40;
|
|
38759
|
+
var MESSAGE_LIMIT = 4e3;
|
|
38760
|
+
var PREVIEW_LIMIT = 90;
|
|
38761
|
+
var HISTORY_PAGE_SIZE = 40;
|
|
38762
|
+
var AGENT_KINDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
38763
|
+
var MAX_PHONE_FILES = 8;
|
|
38764
|
+
var MAX_PHONE_FILE_CHARS = Math.ceil(8 * 1024 * 1024 * 4 / 3);
|
|
38765
|
+
var MAX_PHONE_FILES_CHARS = Math.ceil(20 * 1024 * 1024 * 4 / 3);
|
|
38766
|
+
var MAX_DICTATION_CHARS = Math.ceil(12 * 1024 * 1024 * 4 / 3);
|
|
38767
|
+
function encodePhoneControl(payload) {
|
|
38768
|
+
return PHONE_CONTROL_PREFIX + JSON.stringify(payload);
|
|
38769
|
+
}
|
|
38770
|
+
function takePhoneControl(text6) {
|
|
38771
|
+
if (!text6.startsWith(PHONE_CONTROL_PREFIX)) return null;
|
|
38772
|
+
let parsed;
|
|
38773
|
+
try {
|
|
38774
|
+
parsed = JSON.parse(text6.slice(PHONE_CONTROL_PREFIX.length));
|
|
38775
|
+
} catch {
|
|
38776
|
+
return "invalid";
|
|
38777
|
+
}
|
|
38778
|
+
return parsePhoneControl(parsed);
|
|
38779
|
+
}
|
|
38780
|
+
function parsePhoneControl(value) {
|
|
38781
|
+
if (!value || typeof value !== "object") return "invalid";
|
|
38782
|
+
const op = value.op;
|
|
38783
|
+
if (op === "list") return { op: "list" };
|
|
38784
|
+
if (op === "history") {
|
|
38785
|
+
const query = textField(value, "query");
|
|
38786
|
+
const after = textField(value, "after");
|
|
38787
|
+
return { op: "history", ...query ? { query } : {}, ...after ? { after } : {} };
|
|
38788
|
+
}
|
|
38789
|
+
if (op === "sources") {
|
|
38790
|
+
const repoPath = textField(value, "repoPath");
|
|
38791
|
+
if (!repoPath || !isProjectPath(repoPath)) return "invalid";
|
|
38792
|
+
const query = textField(value, "query");
|
|
38793
|
+
return { op: "sources", repoPath, ...query ? { query } : {} };
|
|
38794
|
+
}
|
|
38795
|
+
if (op === "models") {
|
|
38796
|
+
const agent = agentField(value);
|
|
38797
|
+
if (!agent) return "invalid";
|
|
38798
|
+
return { op: "models", agent };
|
|
38799
|
+
}
|
|
38800
|
+
if (op === "dictate") {
|
|
38801
|
+
const id = textField(value, "id");
|
|
38802
|
+
const audioBase64 = base64Field(value, "audioBase64");
|
|
38803
|
+
if (!id || !audioBase64 || audioBase64.length > MAX_DICTATION_CHARS) return "invalid";
|
|
38804
|
+
return { op: "dictate", id, audioBase64 };
|
|
38805
|
+
}
|
|
38806
|
+
if (op === "create") return parsePhoneCreate(value);
|
|
38807
|
+
const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
|
|
38808
|
+
if (!chatId) return "invalid";
|
|
38809
|
+
if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
|
|
38810
|
+
const draft = parsePhoneDraft(value);
|
|
38811
|
+
if (draft === "invalid") return "invalid";
|
|
38812
|
+
if (op === "options") return { op: "options", chatId, ...draft };
|
|
38813
|
+
if (op === "prompt") {
|
|
38814
|
+
const text6 = typeof value.text === "string" ? value.text.trim() : "";
|
|
38815
|
+
const hasPayload = Boolean(text6 || draft.files?.length || draft.links?.length);
|
|
38816
|
+
if (!hasPayload) return "invalid";
|
|
38817
|
+
return { op: "prompt", chatId, text: text6, ...draft };
|
|
38818
|
+
}
|
|
38819
|
+
return "invalid";
|
|
38820
|
+
}
|
|
38821
|
+
function textField(value, key) {
|
|
38822
|
+
const raw = value[key];
|
|
38823
|
+
return typeof raw === "string" ? raw.trim() : "";
|
|
38824
|
+
}
|
|
38825
|
+
function agentField(value) {
|
|
38826
|
+
const agent = textField(value, "agent");
|
|
38827
|
+
if (!agent) return void 0;
|
|
38828
|
+
return AGENT_KINDS.has(agent) ? agent : void 0;
|
|
38829
|
+
}
|
|
38830
|
+
function base64Field(value, key) {
|
|
38831
|
+
const raw = value[key];
|
|
38832
|
+
if (typeof raw !== "string") return "";
|
|
38833
|
+
return raw.replace(/\s/g, "");
|
|
38834
|
+
}
|
|
38835
|
+
function parsePhoneDraft(value) {
|
|
38836
|
+
const draft = {};
|
|
38837
|
+
if (textField(value, "agent")) {
|
|
38838
|
+
const agent = agentField(value);
|
|
38839
|
+
if (!agent) return "invalid";
|
|
38840
|
+
draft.agent = agent;
|
|
38841
|
+
}
|
|
38842
|
+
if ("model" in value) {
|
|
38843
|
+
const model = value.model;
|
|
38844
|
+
if (model === null || model === "") draft.model = null;
|
|
38845
|
+
else if (typeof model === "string") draft.model = model.trim().slice(0, 200);
|
|
38846
|
+
else return "invalid";
|
|
38847
|
+
}
|
|
38848
|
+
if ("effort" in value && value.effort != null) {
|
|
38849
|
+
const effort = value.effort;
|
|
38850
|
+
if (!isThinkingEffort(effort)) return "invalid";
|
|
38851
|
+
draft.effort = effort;
|
|
38852
|
+
}
|
|
38853
|
+
if ("fast" in value) {
|
|
38854
|
+
const fast = value.fast;
|
|
38855
|
+
if (typeof fast !== "boolean") return "invalid";
|
|
38856
|
+
draft.fast = fast;
|
|
38857
|
+
}
|
|
38858
|
+
if ("planMode" in value) {
|
|
38859
|
+
const planMode = value.planMode;
|
|
38860
|
+
if (typeof planMode !== "boolean") return "invalid";
|
|
38861
|
+
draft.planMode = planMode;
|
|
38862
|
+
}
|
|
38863
|
+
if ("autonomy" in value && value.autonomy != null) {
|
|
38864
|
+
const autonomy = value.autonomy;
|
|
38865
|
+
if (autonomy !== "default" && autonomy !== "full") return "invalid";
|
|
38866
|
+
draft.autonomy = autonomy;
|
|
38867
|
+
}
|
|
38868
|
+
const files = parsePhoneFiles(value.files);
|
|
38869
|
+
if (files === "invalid") return "invalid";
|
|
38870
|
+
if (files.length) draft.files = files;
|
|
38871
|
+
const links = parsePhoneLinks(value.links);
|
|
38872
|
+
if (links === "invalid") return "invalid";
|
|
38873
|
+
if (links.length) draft.links = links;
|
|
38874
|
+
const prompt = textField(value, "prompt");
|
|
38875
|
+
if (prompt) draft.prompt = prompt.slice(0, 2e4);
|
|
38876
|
+
return draft;
|
|
38877
|
+
}
|
|
38878
|
+
function parsePhoneFiles(value) {
|
|
38879
|
+
if (value == null) return [];
|
|
38880
|
+
if (!Array.isArray(value) || value.length > MAX_PHONE_FILES) return "invalid";
|
|
38881
|
+
const files = [];
|
|
38882
|
+
let total = 0;
|
|
38883
|
+
for (const item of value) {
|
|
38884
|
+
if (!item || typeof item !== "object") return "invalid";
|
|
38885
|
+
const name = (textField(item, "name").replace(/[/\\]/g, "_") || "file").slice(0, 180);
|
|
38886
|
+
const dataBase64 = base64Field(item, "dataBase64");
|
|
38887
|
+
if (!dataBase64 || !/^[A-Za-z0-9+/=]+$/.test(dataBase64)) return "invalid";
|
|
38888
|
+
if (dataBase64.length > MAX_PHONE_FILE_CHARS) return "invalid";
|
|
38889
|
+
total += dataBase64.length;
|
|
38890
|
+
if (total > MAX_PHONE_FILES_CHARS) return "invalid";
|
|
38891
|
+
files.push({ name, dataBase64 });
|
|
38892
|
+
}
|
|
38893
|
+
return files;
|
|
38894
|
+
}
|
|
38895
|
+
function parsePhoneLinks(value) {
|
|
38896
|
+
if (value == null) return [];
|
|
38897
|
+
if (!Array.isArray(value) || value.length > 8) return "invalid";
|
|
38898
|
+
const links = [];
|
|
38899
|
+
for (const item of value) {
|
|
38900
|
+
if (!item || typeof item !== "object") return "invalid";
|
|
38901
|
+
const ref = textField(item, "ref").slice(0, 80);
|
|
38902
|
+
if (!ref) return "invalid";
|
|
38903
|
+
const title = (textField(item, "title") || ref).slice(0, 200);
|
|
38904
|
+
const url = textField(item, "url").slice(0, 500);
|
|
38905
|
+
links.push({ ref, title, ...url ? { url } : {} });
|
|
38906
|
+
}
|
|
38907
|
+
return links;
|
|
38908
|
+
}
|
|
38909
|
+
function parsePhoneCreate(value) {
|
|
38910
|
+
const draft = parsePhoneDraft(value);
|
|
38911
|
+
if (draft === "invalid") return "invalid";
|
|
38912
|
+
const goal = textField(value, "goal") || draft.prompt || "";
|
|
38913
|
+
const where = value.where;
|
|
38914
|
+
if (where == null || where === "orchestration") {
|
|
38915
|
+
return {
|
|
38916
|
+
op: "create",
|
|
38917
|
+
where: "orchestration",
|
|
38918
|
+
...draft,
|
|
38919
|
+
...goal ? { goal, prompt: goal } : {}
|
|
38920
|
+
};
|
|
38921
|
+
}
|
|
38922
|
+
if (where === "worktree") {
|
|
38923
|
+
const chatId = textField(value, "chatId");
|
|
38924
|
+
if (!chatId) return "invalid";
|
|
38925
|
+
return { op: "create", where: "worktree", chatId, ...draft };
|
|
38926
|
+
}
|
|
38927
|
+
if (where === "project") {
|
|
38928
|
+
const repoPath = textField(value, "repoPath");
|
|
38929
|
+
if (!repoPath || !isProjectPath(repoPath)) return "invalid";
|
|
38930
|
+
const sourceRaw = textField(value, "sourceType");
|
|
38931
|
+
let sourceType;
|
|
38932
|
+
if (sourceRaw === "branch" || sourceRaw === "pr" || sourceRaw === "ticket") sourceType = sourceRaw;
|
|
38933
|
+
else if (sourceRaw) return "invalid";
|
|
38934
|
+
const sourceRef = textField(value, "sourceRef");
|
|
38935
|
+
if ((sourceType === "pr" || sourceType === "ticket") && !sourceRef) return "invalid";
|
|
38936
|
+
const title = textField(value, "title").slice(0, 200);
|
|
38937
|
+
const cowboy = value.cowboy === true;
|
|
38938
|
+
return {
|
|
38939
|
+
op: "create",
|
|
38940
|
+
where: "project",
|
|
38941
|
+
repoPath,
|
|
38942
|
+
...draft,
|
|
38943
|
+
...sourceType ? { sourceType } : {},
|
|
38944
|
+
...sourceRef ? { sourceRef } : {},
|
|
38945
|
+
...title ? { title } : {},
|
|
38946
|
+
...cowboy ? { cowboy: true } : {}
|
|
38947
|
+
};
|
|
38948
|
+
}
|
|
38949
|
+
return "invalid";
|
|
38950
|
+
}
|
|
38951
|
+
function phoneVisibleText(role, text6) {
|
|
38952
|
+
const trimmed = text6.trim();
|
|
38953
|
+
if (role === "user" && trimmed.startsWith(PHONE_USER_PREFIX)) {
|
|
38954
|
+
return trimmed.slice(PHONE_USER_PREFIX.length).trim();
|
|
38955
|
+
}
|
|
38956
|
+
return trimmed;
|
|
38957
|
+
}
|
|
38958
|
+
function clip(text6, limit) {
|
|
38959
|
+
if (text6.length <= limit) return text6;
|
|
38960
|
+
return `${text6.slice(0, limit - 1)}\u2026`;
|
|
38961
|
+
}
|
|
38962
|
+
function summarize(thread) {
|
|
38963
|
+
return {
|
|
38964
|
+
id: thread.id,
|
|
38965
|
+
title: thread.title?.trim() || "Untitled",
|
|
38966
|
+
status: thread.status,
|
|
38967
|
+
preview: previewOf(thread),
|
|
38968
|
+
updatedAt: thread.updatedAt
|
|
38969
|
+
};
|
|
38970
|
+
}
|
|
38971
|
+
function previewOf(thread) {
|
|
38972
|
+
const messages = listPhoneMessages(thread);
|
|
38973
|
+
const last = messages[messages.length - 1];
|
|
38974
|
+
if (!last) return "";
|
|
38975
|
+
return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
|
|
38976
|
+
}
|
|
38977
|
+
function listPhoneMessages(thread) {
|
|
38978
|
+
const out = [];
|
|
38979
|
+
for (const message of thread.messages) {
|
|
38980
|
+
if (message.role !== "user" && message.role !== "agent") continue;
|
|
38981
|
+
const names = (message.attachments ?? []).map((item) => item.name.trim()).filter(Boolean);
|
|
38982
|
+
const text6 = clip(
|
|
38983
|
+
[phoneVisibleText(message.role, message.text), names.length ? names.join(", ") : ""].filter(Boolean).join("\n"),
|
|
38984
|
+
MESSAGE_LIMIT
|
|
38985
|
+
);
|
|
38986
|
+
if (!text6) continue;
|
|
38987
|
+
out.push({ role: message.role, text: text6 });
|
|
38988
|
+
}
|
|
38989
|
+
for (const prompt of thread.queue) {
|
|
38990
|
+
const text6 = clip(phoneVisibleText("user", prompt), MESSAGE_LIMIT);
|
|
38991
|
+
if (text6) out.push({ role: "user", text: text6 });
|
|
38992
|
+
}
|
|
38993
|
+
return out.slice(-TRANSCRIPT_LIMIT);
|
|
38994
|
+
}
|
|
38995
|
+
function repoName(repoPath) {
|
|
38996
|
+
const parts = repoPath.replace(/\/$/, "").split("/");
|
|
38997
|
+
return parts[parts.length - 1] || repoPath;
|
|
38998
|
+
}
|
|
38999
|
+
function isProjectPath(path2) {
|
|
39000
|
+
if (!path2 || path2 === "/" || path2 === ".") return false;
|
|
39001
|
+
return !isGlobalThread({ repoPath: path2 });
|
|
39002
|
+
}
|
|
39003
|
+
function listPhoneChats() {
|
|
39004
|
+
return listGlobalThreads().slice().sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).map(summarize);
|
|
39005
|
+
}
|
|
39006
|
+
function listPhoneSidebar() {
|
|
39007
|
+
const threads = listThreads();
|
|
39008
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
39009
|
+
for (const thread of threads) {
|
|
39010
|
+
if (!isProjectPath(thread.repoPath)) continue;
|
|
39011
|
+
const list = byRepo.get(thread.repoPath) ?? [];
|
|
39012
|
+
list.push(thread);
|
|
39013
|
+
byRepo.set(thread.repoPath, list);
|
|
39014
|
+
}
|
|
39015
|
+
for (const workspace of listWorkspaces()) {
|
|
39016
|
+
if (!isProjectPath(workspace.path) || byRepo.has(workspace.path)) continue;
|
|
39017
|
+
byRepo.set(workspace.path, []);
|
|
39018
|
+
}
|
|
39019
|
+
const projects = [...byRepo.entries()].sort(([a], [b]) => repoName(a).localeCompare(repoName(b))).map(([path2, repoThreads]) => ({
|
|
39020
|
+
name: repoName(path2),
|
|
39021
|
+
path: path2,
|
|
39022
|
+
worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => ({
|
|
39023
|
+
label: worktreeDisplayLabelForGroup(group),
|
|
39024
|
+
chats: group.map(summarize)
|
|
39025
|
+
}))
|
|
39026
|
+
}));
|
|
39027
|
+
return { orchestration: listPhoneChats(), projects };
|
|
39028
|
+
}
|
|
39029
|
+
function historyStamp(thread) {
|
|
39030
|
+
return thread.archivedAt || thread.updatedAt;
|
|
39031
|
+
}
|
|
39032
|
+
function historyCursor(chat) {
|
|
39033
|
+
return `${chat.updatedAt} ${chat.id}`;
|
|
39034
|
+
}
|
|
39035
|
+
function historyQueryMatch(chat, query) {
|
|
39036
|
+
if (!query) return true;
|
|
39037
|
+
return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
|
|
39038
|
+
}
|
|
39039
|
+
function listPhoneHistory(opts) {
|
|
39040
|
+
const query = opts?.query?.trim().toLowerCase() ?? "";
|
|
39041
|
+
const after = opts?.after?.trim() ?? "";
|
|
39042
|
+
const all = listThreads({ includeArchived: true }).filter((thread) => thread.status === "archived").filter((thread) => isGlobalThread(thread) || isProjectPath(thread.repoPath)).sort((a, b) => historyStamp(a) < historyStamp(b) ? 1 : historyStamp(a) > historyStamp(b) ? -1 : 0).map((thread) => ({
|
|
39043
|
+
id: thread.id,
|
|
39044
|
+
title: thread.title?.trim() || "Untitled",
|
|
39045
|
+
preview: previewOf(thread),
|
|
39046
|
+
updatedAt: historyStamp(thread),
|
|
39047
|
+
where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
|
|
39048
|
+
agent: thread.agent
|
|
39049
|
+
})).filter((chat) => historyQueryMatch(chat, query));
|
|
39050
|
+
let start = 0;
|
|
39051
|
+
if (after) {
|
|
39052
|
+
const index = all.findIndex((chat) => historyCursor(chat) === after);
|
|
39053
|
+
start = index >= 0 ? index + 1 : all.length;
|
|
39054
|
+
}
|
|
39055
|
+
const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
|
|
39056
|
+
const last = chats.at(-1);
|
|
39057
|
+
const more = start + chats.length < all.length;
|
|
39058
|
+
return {
|
|
39059
|
+
chats,
|
|
39060
|
+
total: all.length,
|
|
39061
|
+
...more && last ? { next: historyCursor(last) } : {}
|
|
39062
|
+
};
|
|
39063
|
+
}
|
|
39064
|
+
function readPhoneThread(chatId) {
|
|
39065
|
+
const id = chatId.trim();
|
|
39066
|
+
if (!id) return null;
|
|
39067
|
+
const thread = readThread(id);
|
|
39068
|
+
if (!thread || thread.status === "archived") return null;
|
|
39069
|
+
if (isGlobalThread(thread)) return thread;
|
|
39070
|
+
if (!isProjectPath(thread.repoPath)) return null;
|
|
39071
|
+
return thread;
|
|
39072
|
+
}
|
|
39073
|
+
function phoneComposerOptions(thread) {
|
|
39074
|
+
return {
|
|
39075
|
+
agent: thread.agent,
|
|
39076
|
+
model: thread.model ?? null,
|
|
39077
|
+
effort: thread.effort,
|
|
39078
|
+
fast: Boolean(thread.fast),
|
|
39079
|
+
planMode: Boolean(thread.planMode),
|
|
39080
|
+
autonomy: thread.autonomy ?? "default"
|
|
39081
|
+
};
|
|
39082
|
+
}
|
|
39083
|
+
function phoneTurnAttachments(draft) {
|
|
39084
|
+
const links = (draft.links ?? []).map((link) => ({
|
|
39085
|
+
id: (0, import_node_crypto25.randomUUID)(),
|
|
39086
|
+
name: link.ref,
|
|
39087
|
+
kind: "issue",
|
|
39088
|
+
content: [`Linked issue: ${link.ref} \u2014 ${link.title}`, link.url ? `URL: ${link.url}` : null].filter(Boolean).join("\n")
|
|
39089
|
+
}));
|
|
39090
|
+
const files = (draft.files ?? []).map((file) => ({
|
|
39091
|
+
id: (0, import_node_crypto25.randomUUID)(),
|
|
39092
|
+
name: file.name,
|
|
39093
|
+
kind: "file",
|
|
39094
|
+
previewDataUrl: `data:application/octet-stream;base64,${file.dataBase64}`,
|
|
39095
|
+
content: `File attached: ${file.name}`
|
|
39096
|
+
}));
|
|
39097
|
+
return [...links, ...files];
|
|
39098
|
+
}
|
|
39099
|
+
function openedFrom(thread, place) {
|
|
39100
|
+
const fresh = readThread(thread.id) ?? thread;
|
|
39101
|
+
return {
|
|
39102
|
+
chat: summarize(fresh),
|
|
39103
|
+
messages: listPhoneMessages(fresh),
|
|
39104
|
+
place,
|
|
39105
|
+
options: phoneComposerOptions(fresh)
|
|
39106
|
+
};
|
|
39107
|
+
}
|
|
39108
|
+
function openPhoneChat(chatId) {
|
|
39109
|
+
const thread = readPhoneThread(chatId);
|
|
39110
|
+
if (!thread) return null;
|
|
39111
|
+
const place = isGlobalThread(thread) ? { kind: "orchestration" } : projectPlace(thread);
|
|
39112
|
+
return openedFrom(thread, place);
|
|
39113
|
+
}
|
|
39114
|
+
function sameRepoPath3(a, b) {
|
|
39115
|
+
return a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
|
|
39116
|
+
}
|
|
39117
|
+
function projectPlace(thread) {
|
|
39118
|
+
const group = threadsSharingWorktree(thread.worktreePath);
|
|
39119
|
+
return {
|
|
39120
|
+
kind: "project",
|
|
39121
|
+
repoPath: thread.repoPath,
|
|
39122
|
+
worktree: worktreeDisplayLabelForGroup(group.length > 0 ? group : [thread])
|
|
39123
|
+
};
|
|
39124
|
+
}
|
|
39125
|
+
function assertKnownProject(repoPath) {
|
|
39126
|
+
const path2 = repoPath.trim();
|
|
39127
|
+
if (!isProjectPath(path2)) throw new Error("Pick a project on this Mac.");
|
|
39128
|
+
const known = listWorkspaces().some((workspace) => sameRepoPath3(workspace.path, path2)) || listThreads().some(
|
|
39129
|
+
(thread) => thread.status !== "archived" && sameRepoPath3(thread.repoPath, path2)
|
|
39130
|
+
);
|
|
39131
|
+
if (!known) throw new Error("That project is not on this Mac.");
|
|
39132
|
+
return path2;
|
|
39133
|
+
}
|
|
39134
|
+
function draftAgentOptions(draft, defaults) {
|
|
39135
|
+
return {
|
|
39136
|
+
agent: draft?.agent ?? defaults.agent,
|
|
39137
|
+
model: draft && "model" in draft ? draft.model ?? null : defaults.model,
|
|
39138
|
+
effort: draft?.effort ?? defaults.effort,
|
|
39139
|
+
fast: draft?.fast ?? defaults.fast,
|
|
39140
|
+
planMode: Boolean(draft?.planMode),
|
|
39141
|
+
autonomy: draft?.autonomy
|
|
39142
|
+
};
|
|
39143
|
+
}
|
|
39144
|
+
function createPhoneChat(agent) {
|
|
39145
|
+
const defaults = resolveOrchestratorDefaults();
|
|
39146
|
+
const thread = createGlobalChat({
|
|
39147
|
+
agent: agent ?? defaults.agent,
|
|
39148
|
+
model: defaults.model,
|
|
39149
|
+
effort: defaults.effort,
|
|
39150
|
+
fast: defaults.fast
|
|
39151
|
+
});
|
|
39152
|
+
return openedFrom(thread, { kind: "orchestration" });
|
|
39153
|
+
}
|
|
39154
|
+
async function createPhoneOrchestration(draft) {
|
|
39155
|
+
const goal = (draft?.goal ?? draft?.prompt ?? "").trim();
|
|
39156
|
+
const attachments = phoneTurnAttachments(draft ?? {});
|
|
39157
|
+
if (!goal && attachments.length === 0) return createPhoneChat(draft?.agent);
|
|
39158
|
+
const defaults = resolveOrchestratorDefaults();
|
|
39159
|
+
const options = draftAgentOptions(draft, defaults);
|
|
39160
|
+
const thread = await startOrchestration({
|
|
39161
|
+
goal: goal || "See the attached files.",
|
|
39162
|
+
agent: options.agent,
|
|
39163
|
+
model: options.model,
|
|
39164
|
+
effort: options.effort,
|
|
39165
|
+
fast: options.fast,
|
|
39166
|
+
planMode: options.planMode,
|
|
39167
|
+
autonomy: options.autonomy,
|
|
39168
|
+
attachments
|
|
39169
|
+
});
|
|
39170
|
+
return openedFrom(thread, { kind: "orchestration" });
|
|
39171
|
+
}
|
|
39172
|
+
function createPhoneWorktreeAgent(fromChatId, draft) {
|
|
39173
|
+
const source = readPhoneThread(fromChatId);
|
|
39174
|
+
if (!source || isGlobalThread(source)) throw new Error("That worktree is not on this Mac.");
|
|
39175
|
+
const defaults = resolveNewThreadOptions();
|
|
39176
|
+
const options = draftAgentOptions(draft, defaults);
|
|
39177
|
+
const attachments = persistPendingFileAttachments(
|
|
39178
|
+
source.worktreePath,
|
|
39179
|
+
phoneTurnAttachments(draft ?? {})
|
|
39180
|
+
);
|
|
39181
|
+
const thread = createChatTab({
|
|
39182
|
+
fromThreadId: source.id,
|
|
39183
|
+
agent: options.agent,
|
|
39184
|
+
model: options.model,
|
|
39185
|
+
effort: options.effort,
|
|
39186
|
+
fast: options.fast,
|
|
39187
|
+
autonomy: options.autonomy,
|
|
39188
|
+
attachments
|
|
39189
|
+
});
|
|
39190
|
+
if (draft?.planMode !== void 0) updateThread(thread.id, { planMode: draft.planMode });
|
|
39191
|
+
return openedFrom(thread, projectPlace(readThread(thread.id) ?? thread));
|
|
39192
|
+
}
|
|
39193
|
+
async function createPhoneProjectWorktree(repoPathOrInput) {
|
|
39194
|
+
const input = typeof repoPathOrInput === "string" ? { repoPath: repoPathOrInput } : repoPathOrInput;
|
|
39195
|
+
const path2 = assertKnownProject(input.repoPath);
|
|
39196
|
+
const defaults = resolveNewThreadOptions();
|
|
39197
|
+
const options = draftAgentOptions(input, defaults);
|
|
39198
|
+
const thread = await createThread({
|
|
39199
|
+
sourceType: input.sourceType ?? "branch",
|
|
39200
|
+
sourceRef: input.sourceRef || "default",
|
|
39201
|
+
repoPath: path2,
|
|
39202
|
+
title: input.title,
|
|
39203
|
+
cowboy: input.cowboy,
|
|
39204
|
+
agent: options.agent,
|
|
39205
|
+
model: options.model,
|
|
39206
|
+
effort: options.effort,
|
|
39207
|
+
fast: options.fast,
|
|
39208
|
+
planMode: options.planMode,
|
|
39209
|
+
autonomy: options.autonomy,
|
|
39210
|
+
attachments: phoneTurnAttachments(input),
|
|
39211
|
+
prompt: input.prompt,
|
|
39212
|
+
reuseExisting: false
|
|
39213
|
+
});
|
|
39214
|
+
return openedFrom(thread, projectPlace(thread));
|
|
39215
|
+
}
|
|
39216
|
+
function sourceWarning(label, err) {
|
|
39217
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39218
|
+
return message ? `${label}: ${message}` : `${label} failed.`;
|
|
39219
|
+
}
|
|
39220
|
+
async function listPhoneSources(repoPath, query) {
|
|
39221
|
+
const path2 = assertKnownProject(repoPath);
|
|
39222
|
+
const q = query?.trim() ?? "";
|
|
39223
|
+
const warnings = [];
|
|
39224
|
+
const [prs, branches, issues] = await Promise.all([
|
|
39225
|
+
listPrs(path2, { state: "open", limit: 40, ...q ? { query: q } : {} }).catch((err) => {
|
|
39226
|
+
warnings.push(sourceWarning("Pull requests", err));
|
|
39227
|
+
return [];
|
|
39228
|
+
}),
|
|
39229
|
+
listBranches(path2).catch((err) => {
|
|
39230
|
+
warnings.push(sourceWarning("Branches", err));
|
|
39231
|
+
return [];
|
|
39232
|
+
}),
|
|
39233
|
+
listIssues(path2, { limit: 40, ...q ? { query: q } : {} }).catch((err) => {
|
|
39234
|
+
warnings.push(sourceWarning("Issues", err));
|
|
39235
|
+
return { issues: [] };
|
|
39236
|
+
})
|
|
39237
|
+
]);
|
|
39238
|
+
const needle = q.toLowerCase();
|
|
39239
|
+
const branchRows = branches.filter((branch) => !needle || branch.name.toLowerCase().includes(needle)).slice(0, 50).map((branch) => ({
|
|
39240
|
+
ref: branch.name,
|
|
39241
|
+
title: branch.current ? `${branch.name} (current)` : branch.name
|
|
39242
|
+
}));
|
|
39243
|
+
return {
|
|
39244
|
+
prs: prs.slice(0, 40).map((pr) => ({
|
|
39245
|
+
ref: String(pr.number),
|
|
39246
|
+
title: clip(pr.title || `PR #${pr.number}`, 140),
|
|
39247
|
+
...pr.url ? { url: pr.url } : {}
|
|
39248
|
+
})),
|
|
39249
|
+
branches: branchRows,
|
|
39250
|
+
issues: (issues.issues ?? []).slice(0, 40).map((issue) => ({
|
|
39251
|
+
ref: issue.identifier,
|
|
39252
|
+
title: clip(issue.title || issue.identifier, 140),
|
|
39253
|
+
...issue.url ? { url: issue.url } : {}
|
|
39254
|
+
})),
|
|
39255
|
+
...warnings.length ? { warnings } : {}
|
|
39256
|
+
};
|
|
39257
|
+
}
|
|
39258
|
+
async function listPhoneModels(agent) {
|
|
39259
|
+
const catalogs = await listModelsForAgent(agent);
|
|
39260
|
+
return (catalogs[0]?.models ?? []).slice(0, 40).map((model) => ({
|
|
39261
|
+
id: model.id,
|
|
39262
|
+
label: model.displayName || model.id
|
|
39263
|
+
}));
|
|
39264
|
+
}
|
|
39265
|
+
function archivePhoneChat(chatId) {
|
|
39266
|
+
const thread = readPhoneThread(chatId);
|
|
39267
|
+
if (!thread) return false;
|
|
39268
|
+
updateThread(thread.id, { status: "archived" });
|
|
39269
|
+
return true;
|
|
39270
|
+
}
|
|
39271
|
+
|
|
39272
|
+
// src/remote/listen.ts
|
|
38760
39273
|
var REMOTE_STOPPED_REPLY = "Sideboard stopped the in-progress turn.";
|
|
38761
39274
|
function formatRemotePrompt(text6) {
|
|
38762
39275
|
return `Phone
|
|
@@ -38768,6 +39281,8 @@ function isRemoteStopCommand(text6) {
|
|
|
38768
39281
|
}
|
|
38769
39282
|
var handleChain2 = Promise.resolve();
|
|
38770
39283
|
var inboundGeneration = 0;
|
|
39284
|
+
var chatChains = /* @__PURE__ */ new Map();
|
|
39285
|
+
var chatGeneration = /* @__PURE__ */ new Map();
|
|
38771
39286
|
function enqueue(fn) {
|
|
38772
39287
|
const run2 = handleChain2.then(fn, fn);
|
|
38773
39288
|
handleChain2 = run2.then(
|
|
@@ -38789,10 +39304,340 @@ function interruptRemoteCoordinator(deviceId, log = () => void 0) {
|
|
|
38789
39304
|
return false;
|
|
38790
39305
|
}
|
|
38791
39306
|
}
|
|
39307
|
+
function bumpChat(chatId) {
|
|
39308
|
+
const next = (chatGeneration.get(chatId) ?? 0) + 1;
|
|
39309
|
+
chatGeneration.set(chatId, next);
|
|
39310
|
+
return next;
|
|
39311
|
+
}
|
|
39312
|
+
function enqueueChat(chatId, fn) {
|
|
39313
|
+
const prev = chatChains.get(chatId) ?? Promise.resolve();
|
|
39314
|
+
const run2 = prev.then(fn, fn);
|
|
39315
|
+
chatChains.set(
|
|
39316
|
+
chatId,
|
|
39317
|
+
run2.then(
|
|
39318
|
+
() => void 0,
|
|
39319
|
+
() => void 0
|
|
39320
|
+
)
|
|
39321
|
+
);
|
|
39322
|
+
}
|
|
39323
|
+
function emitControl(opts, reply) {
|
|
39324
|
+
opts.onOutbound({ type: "assistant", text: encodePhoneControl(reply) });
|
|
39325
|
+
}
|
|
39326
|
+
function interruptChat(chatId, log) {
|
|
39327
|
+
try {
|
|
39328
|
+
const thread = readPhoneThread(chatId);
|
|
39329
|
+
if (!thread) return false;
|
|
39330
|
+
if (thread.status !== "running" && thread.status !== "queued") return false;
|
|
39331
|
+
getOrchestrator().stop(thread.id, { clearQueue: true });
|
|
39332
|
+
log(`interrupt phone \u2192 chat ${thread.id.slice(0, 8)} (${thread.status})`);
|
|
39333
|
+
return true;
|
|
39334
|
+
} catch (err) {
|
|
39335
|
+
log(`interrupt chat: ${err instanceof Error ? err.message : String(err)}`);
|
|
39336
|
+
return false;
|
|
39337
|
+
}
|
|
39338
|
+
}
|
|
39339
|
+
function emitChatTurn(opts, chatId, threadId) {
|
|
39340
|
+
const thread = readThread(threadId);
|
|
39341
|
+
if (!thread) {
|
|
39342
|
+
emitControl(opts, { op: "error", chatId, message: "That agent is not on this Mac." });
|
|
39343
|
+
return;
|
|
39344
|
+
}
|
|
39345
|
+
if (thread.status === "stopped") {
|
|
39346
|
+
emitControl(opts, { op: "stopped", chatId });
|
|
39347
|
+
return;
|
|
39348
|
+
}
|
|
39349
|
+
const result = getOrchestrator().getTurnResult(thread.id);
|
|
39350
|
+
const pending = extractPendingPlanQuestions(
|
|
39351
|
+
[...thread.messages].reverse().find((m) => m.role === "agent")?.parts
|
|
39352
|
+
);
|
|
39353
|
+
if (result.taskState === "input-required" && pending) {
|
|
39354
|
+
emitControl(opts, {
|
|
39355
|
+
op: "ask",
|
|
39356
|
+
chatId,
|
|
39357
|
+
text: result.text.trim(),
|
|
39358
|
+
questions: pending.questions.map((q) => ({
|
|
39359
|
+
question: q.question,
|
|
39360
|
+
options: q.options.map(
|
|
39361
|
+
(o) => o.description ? { label: o.label, description: o.description } : { label: o.label }
|
|
39362
|
+
)
|
|
39363
|
+
}))
|
|
39364
|
+
});
|
|
39365
|
+
return;
|
|
39366
|
+
}
|
|
39367
|
+
const reply = outboundReplyFromTurn(result, {
|
|
39368
|
+
inputRequired: "Sideboard is waiting for an answer.",
|
|
39369
|
+
canceled: REMOTE_STOPPED_REPLY,
|
|
39370
|
+
completedEmpty: () => "",
|
|
39371
|
+
failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
|
|
39372
|
+
});
|
|
39373
|
+
emitControl(opts, { op: "assistant", chatId, text: reply.trim() });
|
|
39374
|
+
}
|
|
39375
|
+
function phoneError(err) {
|
|
39376
|
+
return err instanceof Error ? err.message : String(err);
|
|
39377
|
+
}
|
|
39378
|
+
function deliverPhoneTurn(opts, chatId) {
|
|
39379
|
+
const thread = readPhoneThread(chatId);
|
|
39380
|
+
if (!thread || thread.status !== "running" && thread.status !== "queued") return;
|
|
39381
|
+
const generation = bumpChat(chatId);
|
|
39382
|
+
enqueueChat(chatId, async () => {
|
|
39383
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39384
|
+
try {
|
|
39385
|
+
await getOrchestrator().waitForTurn(chatId, 14 * 60 * 1e3);
|
|
39386
|
+
} catch (err) {
|
|
39387
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39388
|
+
emitControl(opts, { op: "error", chatId, message: `Sideboard failed: ${phoneError(err)}` });
|
|
39389
|
+
return;
|
|
39390
|
+
}
|
|
39391
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39392
|
+
emitChatTurn(opts, chatId, chatId);
|
|
39393
|
+
});
|
|
39394
|
+
}
|
|
39395
|
+
function applyPhoneOptions(chatId, draft) {
|
|
39396
|
+
const patch = {};
|
|
39397
|
+
if (draft.agent) patch.agent = draft.agent;
|
|
39398
|
+
if ("model" in draft) patch.model = draft.model ?? null;
|
|
39399
|
+
if (draft.effort) patch.effort = draft.effort;
|
|
39400
|
+
if (draft.fast !== void 0) patch.fast = draft.fast;
|
|
39401
|
+
if (draft.planMode !== void 0) patch.planMode = draft.planMode;
|
|
39402
|
+
if (draft.autonomy) patch.autonomy = draft.autonomy;
|
|
39403
|
+
if (Object.keys(patch).length === 0) return;
|
|
39404
|
+
getOrchestrator().setThreadOptions(chatId, patch);
|
|
39405
|
+
}
|
|
39406
|
+
function stagePhoneTurnFiles(worktreePath, draft) {
|
|
39407
|
+
const links = phoneTurnAttachments({ links: draft.links });
|
|
39408
|
+
const files = stageBuffersAsAttachments(
|
|
39409
|
+
worktreePath,
|
|
39410
|
+
(draft.files ?? []).map((file) => ({ name: file.name, dataBase64: file.dataBase64 }))
|
|
39411
|
+
);
|
|
39412
|
+
return [...links, ...files];
|
|
39413
|
+
}
|
|
39414
|
+
function handlePhoneControl(cmd, opts) {
|
|
39415
|
+
const log = opts.onLog ?? (() => void 0);
|
|
39416
|
+
if (cmd.op === "list") {
|
|
39417
|
+
emitControl(opts, { op: "sidebar", ...listPhoneSidebar() });
|
|
39418
|
+
return;
|
|
39419
|
+
}
|
|
39420
|
+
if (cmd.op === "history") {
|
|
39421
|
+
const page = listPhoneHistory({ query: cmd.query, after: cmd.after });
|
|
39422
|
+
emitControl(opts, {
|
|
39423
|
+
op: "history",
|
|
39424
|
+
chats: page.chats,
|
|
39425
|
+
total: page.total,
|
|
39426
|
+
...page.next ? { next: page.next } : {},
|
|
39427
|
+
...cmd.query ? { query: cmd.query } : {},
|
|
39428
|
+
...cmd.after ? { after: cmd.after } : {}
|
|
39429
|
+
});
|
|
39430
|
+
return;
|
|
39431
|
+
}
|
|
39432
|
+
if (cmd.op === "sources") {
|
|
39433
|
+
const repoPath = cmd.repoPath;
|
|
39434
|
+
const query = cmd.query;
|
|
39435
|
+
void listPhoneSources(repoPath, query).then(
|
|
39436
|
+
(page) => emitControl(opts, {
|
|
39437
|
+
op: "sources",
|
|
39438
|
+
repoPath,
|
|
39439
|
+
...query ? { query } : {},
|
|
39440
|
+
...page
|
|
39441
|
+
})
|
|
39442
|
+
).catch((err) => {
|
|
39443
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39444
|
+
});
|
|
39445
|
+
return;
|
|
39446
|
+
}
|
|
39447
|
+
if (cmd.op === "models") {
|
|
39448
|
+
const agent = cmd.agent;
|
|
39449
|
+
void listPhoneModels(agent).then((models) => emitControl(opts, { op: "models", agent, models })).catch((err) => {
|
|
39450
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39451
|
+
});
|
|
39452
|
+
return;
|
|
39453
|
+
}
|
|
39454
|
+
if (cmd.op === "dictate") {
|
|
39455
|
+
const id = cmd.id;
|
|
39456
|
+
const transcribe = opts.transcribeWav;
|
|
39457
|
+
if (!transcribe) {
|
|
39458
|
+
emitControl(opts, { op: "error", message: "Speech is not available on this Mac." });
|
|
39459
|
+
return;
|
|
39460
|
+
}
|
|
39461
|
+
void transcribe(cmd.audioBase64).then((text6) => emitControl(opts, { op: "dictated", id, text: text6.trim() })).catch((err) => {
|
|
39462
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39463
|
+
});
|
|
39464
|
+
return;
|
|
39465
|
+
}
|
|
39466
|
+
if (cmd.op === "options") {
|
|
39467
|
+
const thread = readPhoneThread(cmd.chatId);
|
|
39468
|
+
if (!thread) {
|
|
39469
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
39470
|
+
return;
|
|
39471
|
+
}
|
|
39472
|
+
try {
|
|
39473
|
+
applyPhoneOptions(thread.id, cmd);
|
|
39474
|
+
const fresh = readPhoneThread(thread.id) ?? thread;
|
|
39475
|
+
emitControl(opts, { op: "options", chatId: fresh.id, ...phoneComposerOptions(fresh) });
|
|
39476
|
+
} catch (err) {
|
|
39477
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: phoneError(err) });
|
|
39478
|
+
}
|
|
39479
|
+
return;
|
|
39480
|
+
}
|
|
39481
|
+
if (cmd.op === "create") {
|
|
39482
|
+
if (cmd.where === "worktree") {
|
|
39483
|
+
void (async () => {
|
|
39484
|
+
try {
|
|
39485
|
+
const opened = createPhoneWorktreeAgent(cmd.chatId, cmd);
|
|
39486
|
+
const prompt = cmd.prompt?.trim() || (cmd.files?.length || cmd.links?.length ? "See the attached files." : "");
|
|
39487
|
+
if (prompt) {
|
|
39488
|
+
try {
|
|
39489
|
+
await getOrchestrator().send(opened.chat.id, formatRemotePrompt(prompt));
|
|
39490
|
+
} catch (err) {
|
|
39491
|
+
emitControl(opts, { op: "error", chatId: opened.chat.id, message: phoneError(err) });
|
|
39492
|
+
}
|
|
39493
|
+
}
|
|
39494
|
+
const fresh = openPhoneChat(opened.chat.id) ?? opened;
|
|
39495
|
+
emitControl(opts, { op: "opened", ...fresh });
|
|
39496
|
+
if (prompt) deliverPhoneTurn(opts, fresh.chat.id);
|
|
39497
|
+
} catch (err) {
|
|
39498
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39499
|
+
}
|
|
39500
|
+
})();
|
|
39501
|
+
return;
|
|
39502
|
+
}
|
|
39503
|
+
if (cmd.where === "project") {
|
|
39504
|
+
void createPhoneProjectWorktree(cmd).then(async (opened) => {
|
|
39505
|
+
const prompt = resolveCreateFirstPrompt({
|
|
39506
|
+
sourceType: cmd.sourceType ?? "branch",
|
|
39507
|
+
prompt: cmd.prompt
|
|
39508
|
+
}) || (cmd.files?.length || cmd.links?.length ? "See the attached files." : "");
|
|
39509
|
+
if (prompt) {
|
|
39510
|
+
try {
|
|
39511
|
+
await getOrchestrator().send(opened.chat.id, formatRemotePrompt(prompt));
|
|
39512
|
+
} catch (err) {
|
|
39513
|
+
emitControl(opts, { op: "error", chatId: opened.chat.id, message: phoneError(err) });
|
|
39514
|
+
}
|
|
39515
|
+
}
|
|
39516
|
+
const fresh = openPhoneChat(opened.chat.id) ?? opened;
|
|
39517
|
+
emitControl(opts, { op: "opened", ...fresh });
|
|
39518
|
+
if (prompt) deliverPhoneTurn(opts, fresh.chat.id);
|
|
39519
|
+
}).catch((err) => {
|
|
39520
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39521
|
+
});
|
|
39522
|
+
return;
|
|
39523
|
+
}
|
|
39524
|
+
const goal = cmd.goal?.trim() || cmd.prompt?.trim() || "";
|
|
39525
|
+
if (goal || cmd.files?.length || cmd.links?.length) {
|
|
39526
|
+
void createPhoneOrchestration({ ...cmd, goal }).then((opened) => {
|
|
39527
|
+
emitControl(opts, { op: "opened", ...opened });
|
|
39528
|
+
deliverPhoneTurn(opts, opened.chat.id);
|
|
39529
|
+
}).catch((err) => {
|
|
39530
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39531
|
+
});
|
|
39532
|
+
return;
|
|
39533
|
+
}
|
|
39534
|
+
try {
|
|
39535
|
+
const agent = coerceOrchestratorAgent(
|
|
39536
|
+
cmd.agent ?? opts.agent ?? resolveOrchestratorDefaults().agent
|
|
39537
|
+
);
|
|
39538
|
+
emitControl(opts, { op: "opened", ...createPhoneChat(agent) });
|
|
39539
|
+
} catch (err) {
|
|
39540
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39541
|
+
}
|
|
39542
|
+
return;
|
|
39543
|
+
}
|
|
39544
|
+
if (cmd.op === "open") {
|
|
39545
|
+
const opened = openPhoneChat(cmd.chatId);
|
|
39546
|
+
if (!opened) {
|
|
39547
|
+
emitControl(opts, { op: "error", message: "That agent is not on this Mac." });
|
|
39548
|
+
return;
|
|
39549
|
+
}
|
|
39550
|
+
emitControl(opts, { op: "opened", ...opened });
|
|
39551
|
+
return;
|
|
39552
|
+
}
|
|
39553
|
+
if (cmd.op === "archive") {
|
|
39554
|
+
const thread = readPhoneThread(cmd.chatId);
|
|
39555
|
+
if (!thread) {
|
|
39556
|
+
emitControl(opts, { op: "error", message: "That agent is not on this Mac." });
|
|
39557
|
+
return;
|
|
39558
|
+
}
|
|
39559
|
+
bumpChat(thread.id);
|
|
39560
|
+
interruptChat(thread.id, log);
|
|
39561
|
+
archivePhoneChat(thread.id);
|
|
39562
|
+
emitControl(opts, { op: "sidebar", ...listPhoneSidebar() });
|
|
39563
|
+
return;
|
|
39564
|
+
}
|
|
39565
|
+
if (cmd.op === "restore") {
|
|
39566
|
+
const chatId2 = cmd.chatId;
|
|
39567
|
+
void getOrchestrator().restore(chatId2).then(() => {
|
|
39568
|
+
emitControl(opts, { op: "sidebar", ...listPhoneSidebar() });
|
|
39569
|
+
emitControl(opts, { op: "restored", chatId: chatId2 });
|
|
39570
|
+
}).catch((err) => {
|
|
39571
|
+
emitControl(opts, {
|
|
39572
|
+
op: "error",
|
|
39573
|
+
chatId: chatId2,
|
|
39574
|
+
message: err instanceof Error ? err.message : String(err)
|
|
39575
|
+
});
|
|
39576
|
+
});
|
|
39577
|
+
return;
|
|
39578
|
+
}
|
|
39579
|
+
if (cmd.op === "stop") {
|
|
39580
|
+
if (!readPhoneThread(cmd.chatId)) {
|
|
39581
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
39582
|
+
return;
|
|
39583
|
+
}
|
|
39584
|
+
bumpChat(cmd.chatId);
|
|
39585
|
+
interruptChat(cmd.chatId, log);
|
|
39586
|
+
emitControl(opts, { op: "stopped", chatId: cmd.chatId });
|
|
39587
|
+
return;
|
|
39588
|
+
}
|
|
39589
|
+
const chatId = cmd.chatId;
|
|
39590
|
+
const generation = bumpChat(chatId);
|
|
39591
|
+
interruptChat(chatId, log);
|
|
39592
|
+
enqueueChat(chatId, async () => {
|
|
39593
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39594
|
+
const thread = readPhoneThread(chatId);
|
|
39595
|
+
if (!thread) {
|
|
39596
|
+
emitControl(opts, { op: "error", chatId, message: "That agent is not on this Mac." });
|
|
39597
|
+
return;
|
|
39598
|
+
}
|
|
39599
|
+
if (thread.status === "running" || thread.status === "queued") {
|
|
39600
|
+
try {
|
|
39601
|
+
getOrchestrator().stop(thread.id, { clearQueue: true });
|
|
39602
|
+
} catch {
|
|
39603
|
+
}
|
|
39604
|
+
}
|
|
39605
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39606
|
+
try {
|
|
39607
|
+
try {
|
|
39608
|
+
applyPhoneOptions(thread.id, cmd);
|
|
39609
|
+
} catch (err) {
|
|
39610
|
+
emitControl(opts, { op: "error", chatId, message: phoneError(err) });
|
|
39611
|
+
}
|
|
39612
|
+
const live = readPhoneThread(thread.id) ?? thread;
|
|
39613
|
+
const staged = stagePhoneTurnFiles(live.worktreePath, cmd);
|
|
39614
|
+
const text6 = cmd.text.trim() || (staged.length ? "See the attached files." : "");
|
|
39615
|
+
const formatted = formatRemotePrompt(text6);
|
|
39616
|
+
await getOrchestrator().send(thread.id, formatted, { attachments: staged });
|
|
39617
|
+
await getOrchestrator().waitForTurn(thread.id, 14 * 60 * 1e3);
|
|
39618
|
+
} catch (err) {
|
|
39619
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39620
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39621
|
+
emitControl(opts, { op: "error", chatId, message: `Sideboard failed: ${message}` });
|
|
39622
|
+
return;
|
|
39623
|
+
}
|
|
39624
|
+
if (chatGeneration.get(chatId) !== generation) return;
|
|
39625
|
+
emitChatTurn(opts, chatId, thread.id);
|
|
39626
|
+
});
|
|
39627
|
+
}
|
|
38792
39628
|
function handleRemoteInbound(text6, opts) {
|
|
38793
|
-
const generation = ++inboundGeneration;
|
|
38794
39629
|
const log = opts.onLog ?? (() => void 0);
|
|
38795
39630
|
const body = text6.trim();
|
|
39631
|
+
const control = takePhoneControl(body);
|
|
39632
|
+
if (control) {
|
|
39633
|
+
if (control === "invalid") {
|
|
39634
|
+
emitControl(opts, { op: "error", message: "invalid phone command" });
|
|
39635
|
+
return;
|
|
39636
|
+
}
|
|
39637
|
+
handlePhoneControl(control, opts);
|
|
39638
|
+
return;
|
|
39639
|
+
}
|
|
39640
|
+
const generation = ++inboundGeneration;
|
|
38796
39641
|
if (body) interruptRemoteCoordinator(opts.deviceId, log);
|
|
38797
39642
|
enqueue(async () => {
|
|
38798
39643
|
if (generation !== inboundGeneration) return;
|
|
@@ -38907,7 +39752,8 @@ function runRemoteHost(opts) {
|
|
|
38907
39752
|
type: "host_register",
|
|
38908
39753
|
deviceId: opts.deviceId,
|
|
38909
39754
|
deviceLabel,
|
|
38910
|
-
hostSecret: opts.hostSecret
|
|
39755
|
+
hostSecret: opts.hostSecret,
|
|
39756
|
+
...opts.accountToken?.trim() ? { accountToken: opts.accountToken.trim() } : {}
|
|
38911
39757
|
});
|
|
38912
39758
|
};
|
|
38913
39759
|
const loop = async () => {
|
|
@@ -39023,7 +39869,8 @@ function runRemoteHost(opts) {
|
|
|
39023
39869
|
handleRemoteInbound(msg.text, {
|
|
39024
39870
|
deviceId: opts.deviceId,
|
|
39025
39871
|
onLog: log,
|
|
39026
|
-
onOutbound: emit
|
|
39872
|
+
onOutbound: emit,
|
|
39873
|
+
transcribeWav: opts.transcribeWav
|
|
39027
39874
|
});
|
|
39028
39875
|
}
|
|
39029
39876
|
};
|
|
@@ -39050,6 +39897,606 @@ function runRemoteHost(opts) {
|
|
|
39050
39897
|
};
|
|
39051
39898
|
}
|
|
39052
39899
|
|
|
39900
|
+
// src/relay/account-login.ts
|
|
39901
|
+
var import_node_crypto27 = require("crypto");
|
|
39902
|
+
|
|
39903
|
+
// src/relay/accounts.ts
|
|
39904
|
+
var import_node_crypto26 = require("crypto");
|
|
39905
|
+
var import_node_fs70 = require("fs");
|
|
39906
|
+
var import_node_module7 = require("module");
|
|
39907
|
+
var import_node_path68 = require("path");
|
|
39908
|
+
var GITHUB_ACCOUNT_ISSUER = "https://github.com";
|
|
39909
|
+
var RELAY_DEVICE_CAP = 3;
|
|
39910
|
+
function thisModuleFile3() {
|
|
39911
|
+
const cjsFile = typeof __filename !== "undefined" ? __filename : "";
|
|
39912
|
+
return cjsFile || process.argv[1] || (0, import_node_path68.join)(process.cwd(), "package.json");
|
|
39913
|
+
}
|
|
39914
|
+
function openDatabase(filePath) {
|
|
39915
|
+
const req = (0, import_node_module7.createRequire)(thisModuleFile3());
|
|
39916
|
+
const specifier = "node:sqlite";
|
|
39917
|
+
const { DatabaseSync: SqliteDatabase } = req(specifier);
|
|
39918
|
+
const db = new SqliteDatabase(filePath);
|
|
39919
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
39920
|
+
return db;
|
|
39921
|
+
}
|
|
39922
|
+
function sha256(value) {
|
|
39923
|
+
return (0, import_node_crypto26.createHash)("sha256").update(value).digest("hex");
|
|
39924
|
+
}
|
|
39925
|
+
function sameHash(a, b) {
|
|
39926
|
+
const left = Buffer.from(a);
|
|
39927
|
+
const right = Buffer.from(b);
|
|
39928
|
+
return left.length === right.length && (0, import_node_crypto26.timingSafeEqual)(left, right);
|
|
39929
|
+
}
|
|
39930
|
+
function normalizeEmail(email) {
|
|
39931
|
+
const trimmed = email?.trim().toLowerCase() ?? "";
|
|
39932
|
+
return trimmed || null;
|
|
39933
|
+
}
|
|
39934
|
+
function longestMountPoint(dir, mounts) {
|
|
39935
|
+
let best = "/";
|
|
39936
|
+
for (const line of mounts.split("\n")) {
|
|
39937
|
+
const point = line.split(" ")[1]?.replace(/\\040/g, " ");
|
|
39938
|
+
if (!point) continue;
|
|
39939
|
+
if (dir === point || dir.startsWith(point.endsWith("/") ? point : `${point}/`)) {
|
|
39940
|
+
if (point.length > best.length) best = point;
|
|
39941
|
+
}
|
|
39942
|
+
}
|
|
39943
|
+
return best;
|
|
39944
|
+
}
|
|
39945
|
+
function assertRelayAccountsDurable(filePath) {
|
|
39946
|
+
if (process.env.SIDEBOARD_RELAY_ACCOUNTS_EPHEMERAL === "1") return;
|
|
39947
|
+
if (process.env.NODE_ENV !== "production") return;
|
|
39948
|
+
if (process.platform !== "linux") return;
|
|
39949
|
+
const dir = (0, import_node_fs70.realpathSync)((0, import_node_path68.dirname)(filePath));
|
|
39950
|
+
const mounts = (0, import_node_fs70.readFileSync)("/proc/mounts", "utf8");
|
|
39951
|
+
const point = longestMountPoint(dir, mounts);
|
|
39952
|
+
if (point === "/") {
|
|
39953
|
+
throw new Error(
|
|
39954
|
+
`Relay accounts at ${filePath} are on the machine root filesystem. Mount a Fly volume so sign-ins survive deploys.`
|
|
39955
|
+
);
|
|
39956
|
+
}
|
|
39957
|
+
}
|
|
39958
|
+
var RelayAccountStore = class {
|
|
39959
|
+
db;
|
|
39960
|
+
constructor(filePath) {
|
|
39961
|
+
(0, import_node_fs70.mkdirSync)((0, import_node_path68.dirname)(filePath), { recursive: true });
|
|
39962
|
+
this.db = openDatabase(filePath);
|
|
39963
|
+
this.db.exec(`
|
|
39964
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
39965
|
+
id TEXT PRIMARY KEY,
|
|
39966
|
+
created_at INTEGER NOT NULL,
|
|
39967
|
+
device_cap INTEGER NOT NULL
|
|
39968
|
+
);
|
|
39969
|
+
CREATE TABLE IF NOT EXISTS identities (
|
|
39970
|
+
issuer TEXT NOT NULL,
|
|
39971
|
+
subject TEXT NOT NULL,
|
|
39972
|
+
account_id TEXT NOT NULL,
|
|
39973
|
+
email TEXT,
|
|
39974
|
+
email_verified INTEGER NOT NULL,
|
|
39975
|
+
PRIMARY KEY (issuer, subject)
|
|
39976
|
+
);
|
|
39977
|
+
CREATE INDEX IF NOT EXISTS identities_email ON identities(email);
|
|
39978
|
+
CREATE TABLE IF NOT EXISTS credentials (
|
|
39979
|
+
token_hash TEXT PRIMARY KEY,
|
|
39980
|
+
account_id TEXT NOT NULL,
|
|
39981
|
+
created_at INTEGER NOT NULL
|
|
39982
|
+
);
|
|
39983
|
+
CREATE TABLE IF NOT EXISTS devices (
|
|
39984
|
+
device_id TEXT PRIMARY KEY,
|
|
39985
|
+
account_id TEXT NOT NULL,
|
|
39986
|
+
host_secret_hash TEXT NOT NULL,
|
|
39987
|
+
device_label TEXT NOT NULL,
|
|
39988
|
+
created_at INTEGER NOT NULL
|
|
39989
|
+
);
|
|
39990
|
+
`);
|
|
39991
|
+
}
|
|
39992
|
+
close() {
|
|
39993
|
+
this.db.close();
|
|
39994
|
+
}
|
|
39995
|
+
/**
|
|
39996
|
+
* First sign-in creates an account. The same issuer+subject returns that
|
|
39997
|
+
* account. A verified email match attaches a second provider. `linkAccountId`
|
|
39998
|
+
* attaches while that account’s credential is already signed in.
|
|
39999
|
+
* Each call mints a new Mac credential. Older credentials stay valid.
|
|
40000
|
+
*/
|
|
40001
|
+
signIn(identity, linkAccountId) {
|
|
40002
|
+
const issuer = identity.issuer.trim();
|
|
40003
|
+
const subject = identity.subject.trim();
|
|
40004
|
+
if (!issuer || !subject) throw new Error("Sign-in is missing an identity.");
|
|
40005
|
+
const email = normalizeEmail(identity.email);
|
|
40006
|
+
const verified = Boolean(identity.emailVerified && email);
|
|
40007
|
+
const link = linkAccountId?.trim() || null;
|
|
40008
|
+
this.db.exec("BEGIN");
|
|
40009
|
+
try {
|
|
40010
|
+
const existing = this.db.prepare("SELECT account_id, email FROM identities WHERE issuer = ? AND subject = ?").get(issuer, subject);
|
|
40011
|
+
let accountId;
|
|
40012
|
+
if (existing) {
|
|
40013
|
+
if (link && link !== existing.account_id) {
|
|
40014
|
+
throw new Error("That sign-in already belongs to another Sideboard account.");
|
|
40015
|
+
}
|
|
40016
|
+
accountId = existing.account_id;
|
|
40017
|
+
this.requireAccount(accountId);
|
|
40018
|
+
if (email) {
|
|
40019
|
+
this.db.prepare("UPDATE identities SET email = ?, email_verified = ? WHERE issuer = ? AND subject = ?").run(email, verified ? 1 : 0, issuer, subject);
|
|
40020
|
+
}
|
|
40021
|
+
} else if (link) {
|
|
40022
|
+
this.requireAccount(link);
|
|
40023
|
+
accountId = link;
|
|
40024
|
+
this.insertIdentity(issuer, subject, accountId, email, verified);
|
|
40025
|
+
} else {
|
|
40026
|
+
const byEmail = verified ? this.accountIdForVerifiedEmail(email) : void 0;
|
|
40027
|
+
accountId = byEmail ?? (0, import_node_crypto26.randomUUID)();
|
|
40028
|
+
if (!byEmail) {
|
|
40029
|
+
this.db.prepare("INSERT INTO accounts (id, created_at, device_cap) VALUES (?, ?, ?)").run(accountId, Date.now(), RELAY_DEVICE_CAP);
|
|
40030
|
+
}
|
|
40031
|
+
this.insertIdentity(issuer, subject, accountId, email, verified);
|
|
40032
|
+
}
|
|
40033
|
+
const credential = this.mintCredential(accountId);
|
|
40034
|
+
const stored = this.db.prepare("SELECT email FROM identities WHERE account_id = ? AND email IS NOT NULL ORDER BY email_verified DESC LIMIT 1").get(accountId);
|
|
40035
|
+
this.db.exec("COMMIT");
|
|
40036
|
+
return { accountId, credential, email: stored?.email ?? email };
|
|
40037
|
+
} catch (err) {
|
|
40038
|
+
this.db.exec("ROLLBACK");
|
|
40039
|
+
throw err;
|
|
40040
|
+
}
|
|
40041
|
+
}
|
|
40042
|
+
verifyCredential(token) {
|
|
40043
|
+
const trimmed = token.trim();
|
|
40044
|
+
if (!trimmed) return null;
|
|
40045
|
+
const row = this.db.prepare("SELECT account_id FROM credentials WHERE token_hash = ?").get(sha256(trimmed));
|
|
40046
|
+
return row?.account_id ?? null;
|
|
40047
|
+
}
|
|
40048
|
+
/** Drop one Mac credential. The account and its other Macs stay. */
|
|
40049
|
+
logout(token) {
|
|
40050
|
+
const trimmed = token.trim();
|
|
40051
|
+
if (!trimmed) return;
|
|
40052
|
+
this.db.prepare("DELETE FROM credentials WHERE token_hash = ?").run(sha256(trimmed));
|
|
40053
|
+
}
|
|
40054
|
+
/** Delete the account and every identity, credential, and Mac bound to it. */
|
|
40055
|
+
revokeAccount(accountId) {
|
|
40056
|
+
const id = accountId.trim();
|
|
40057
|
+
if (!id) return;
|
|
40058
|
+
this.db.exec("BEGIN");
|
|
40059
|
+
try {
|
|
40060
|
+
this.db.prepare("DELETE FROM devices WHERE account_id = ?").run(id);
|
|
40061
|
+
this.db.prepare("DELETE FROM credentials WHERE account_id = ?").run(id);
|
|
40062
|
+
this.db.prepare("DELETE FROM identities WHERE account_id = ?").run(id);
|
|
40063
|
+
this.db.prepare("DELETE FROM accounts WHERE id = ?").run(id);
|
|
40064
|
+
this.db.exec("COMMIT");
|
|
40065
|
+
} catch (err) {
|
|
40066
|
+
this.db.exec("ROLLBACK");
|
|
40067
|
+
throw err;
|
|
40068
|
+
}
|
|
40069
|
+
}
|
|
40070
|
+
/**
|
|
40071
|
+
* Accept `host_register` for a signed-in account. The device row survives
|
|
40072
|
+
* process restart, so a later register with a different host secret is rejected
|
|
40073
|
+
* and a new device id counts against the cap.
|
|
40074
|
+
*/
|
|
40075
|
+
authorizeHost(input) {
|
|
40076
|
+
const deviceId = input.deviceId.trim();
|
|
40077
|
+
const hostSecret = input.hostSecret.trim();
|
|
40078
|
+
const deviceLabel = input.deviceLabel.trim() || "This Mac";
|
|
40079
|
+
if (!deviceId || !hostSecret) return { ok: false, message: "sign in required" };
|
|
40080
|
+
const accountId = this.verifyCredential(input.accountToken);
|
|
40081
|
+
if (!accountId) return { ok: false, message: "sign in required" };
|
|
40082
|
+
this.db.exec("BEGIN");
|
|
40083
|
+
try {
|
|
40084
|
+
const account = this.db.prepare("SELECT id, device_cap FROM accounts WHERE id = ?").get(accountId);
|
|
40085
|
+
if (!account) {
|
|
40086
|
+
this.db.exec("ROLLBACK");
|
|
40087
|
+
return { ok: false, message: "sign in required" };
|
|
40088
|
+
}
|
|
40089
|
+
const device = this.db.prepare("SELECT device_id, account_id, host_secret_hash FROM devices WHERE device_id = ?").get(deviceId);
|
|
40090
|
+
const secretHash = sha256(hostSecret);
|
|
40091
|
+
if (!device) {
|
|
40092
|
+
const count = this.db.prepare("SELECT COUNT(*) AS n FROM devices WHERE account_id = ?").get(accountId);
|
|
40093
|
+
if (count.n >= account.device_cap) {
|
|
40094
|
+
this.db.exec("ROLLBACK");
|
|
40095
|
+
return { ok: false, message: "This account already has the maximum number of Macs." };
|
|
40096
|
+
}
|
|
40097
|
+
this.db.prepare(
|
|
40098
|
+
"INSERT INTO devices (device_id, account_id, host_secret_hash, device_label, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
40099
|
+
).run(deviceId, accountId, secretHash, deviceLabel, Date.now());
|
|
40100
|
+
this.db.exec("COMMIT");
|
|
40101
|
+
return { ok: true, accountId };
|
|
40102
|
+
}
|
|
40103
|
+
if (device.account_id !== accountId) {
|
|
40104
|
+
this.db.exec("ROLLBACK");
|
|
40105
|
+
return { ok: false, message: "This Mac is registered to another account." };
|
|
40106
|
+
}
|
|
40107
|
+
if (!sameHash(device.host_secret_hash, secretHash)) {
|
|
40108
|
+
this.db.exec("ROLLBACK");
|
|
40109
|
+
return { ok: false, message: "host secret rejected" };
|
|
40110
|
+
}
|
|
40111
|
+
this.db.prepare("UPDATE devices SET device_label = ? WHERE device_id = ?").run(deviceLabel, deviceId);
|
|
40112
|
+
this.db.exec("COMMIT");
|
|
40113
|
+
return { ok: true, accountId };
|
|
40114
|
+
} catch (err) {
|
|
40115
|
+
this.db.exec("ROLLBACK");
|
|
40116
|
+
throw err;
|
|
40117
|
+
}
|
|
40118
|
+
}
|
|
40119
|
+
requireAccount(accountId) {
|
|
40120
|
+
const row = this.db.prepare("SELECT id FROM accounts WHERE id = ?").get(accountId);
|
|
40121
|
+
if (!row) throw new Error("That Sideboard account is no longer on the relay.");
|
|
40122
|
+
}
|
|
40123
|
+
accountIdForVerifiedEmail(email) {
|
|
40124
|
+
const row = this.db.prepare(
|
|
40125
|
+
"SELECT account_id FROM identities WHERE email_verified = 1 AND email = ? LIMIT 1"
|
|
40126
|
+
).get(email);
|
|
40127
|
+
return row?.account_id;
|
|
40128
|
+
}
|
|
40129
|
+
insertIdentity(issuer, subject, accountId, email, verified) {
|
|
40130
|
+
this.db.prepare(
|
|
40131
|
+
"INSERT INTO identities (issuer, subject, account_id, email, email_verified) VALUES (?, ?, ?, ?, ?)"
|
|
40132
|
+
).run(issuer, subject, accountId, email, verified ? 1 : 0);
|
|
40133
|
+
}
|
|
40134
|
+
mintCredential(accountId) {
|
|
40135
|
+
const credential = (0, import_node_crypto26.randomBytes)(32).toString("hex");
|
|
40136
|
+
this.db.prepare("INSERT INTO credentials (token_hash, account_id, created_at) VALUES (?, ?, ?)").run(sha256(credential), accountId, Date.now());
|
|
40137
|
+
return credential;
|
|
40138
|
+
}
|
|
40139
|
+
};
|
|
40140
|
+
|
|
40141
|
+
// src/relay/account-oauth.ts
|
|
40142
|
+
var RELAY_GIT_HOSTS = ["github"];
|
|
40143
|
+
var REMOTE_LOGIN_PATH = "/remote/login";
|
|
40144
|
+
var REMOTE_OAUTH_CALLBACK_PATH = "/remote/oauth/callback";
|
|
40145
|
+
var REMOTE_OAUTH_RESULT_PATH = "/remote/oauth/result";
|
|
40146
|
+
var REMOTE_LOGOUT_PATH = "/remote/logout";
|
|
40147
|
+
var STATE_RE = /^[A-Za-z0-9_-]{16,128}$/;
|
|
40148
|
+
var LOGIN_TTL_MS = 10 * 60 * 1e3;
|
|
40149
|
+
var RelayLoginSessions = class {
|
|
40150
|
+
waiting = /* @__PURE__ */ new Map();
|
|
40151
|
+
results = /* @__PURE__ */ new Map();
|
|
40152
|
+
begin(state, provider, accountId) {
|
|
40153
|
+
this.sweep();
|
|
40154
|
+
this.waiting.set(state, { provider, accountId, expiresAt: Date.now() + LOGIN_TTL_MS });
|
|
40155
|
+
}
|
|
40156
|
+
takeLogin(state) {
|
|
40157
|
+
this.sweep();
|
|
40158
|
+
const row = this.waiting.get(state);
|
|
40159
|
+
if (!row) return null;
|
|
40160
|
+
this.waiting.delete(state);
|
|
40161
|
+
if (row.expiresAt < Date.now()) return null;
|
|
40162
|
+
return row;
|
|
40163
|
+
}
|
|
40164
|
+
putResult(state, value) {
|
|
40165
|
+
this.results.set(state, { value, expiresAt: Date.now() + LOGIN_TTL_MS });
|
|
40166
|
+
}
|
|
40167
|
+
/** `undefined` means the browser flow has not finished. */
|
|
40168
|
+
takeResult(state) {
|
|
40169
|
+
this.sweep();
|
|
40170
|
+
const row = this.results.get(state);
|
|
40171
|
+
if (!row) return this.waiting.has(state) ? void 0 : null;
|
|
40172
|
+
this.results.delete(state);
|
|
40173
|
+
if (row.expiresAt < Date.now()) return null;
|
|
40174
|
+
return row.value;
|
|
40175
|
+
}
|
|
40176
|
+
sweep() {
|
|
40177
|
+
const now = Date.now();
|
|
40178
|
+
for (const [state, row] of this.waiting) {
|
|
40179
|
+
if (row.expiresAt < now) this.waiting.delete(state);
|
|
40180
|
+
}
|
|
40181
|
+
for (const [state, row] of this.results) {
|
|
40182
|
+
if (row.expiresAt < now) this.results.delete(state);
|
|
40183
|
+
}
|
|
40184
|
+
}
|
|
40185
|
+
};
|
|
40186
|
+
var GIT_HOST_AUTH = {
|
|
40187
|
+
github: {
|
|
40188
|
+
label: "GitHub",
|
|
40189
|
+
authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
40190
|
+
scope: "user:email",
|
|
40191
|
+
fetchIdentity: fetchGithubIdentity
|
|
40192
|
+
}
|
|
40193
|
+
};
|
|
40194
|
+
function providerOf(value) {
|
|
40195
|
+
return RELAY_GIT_HOSTS.includes(value) ? value : null;
|
|
40196
|
+
}
|
|
40197
|
+
function relayAuthorizeUrl(provider, client, redirectUri, state) {
|
|
40198
|
+
const host = GIT_HOST_AUTH[provider];
|
|
40199
|
+
const url = new URL(host.authorizeUrl);
|
|
40200
|
+
url.searchParams.set("client_id", client.clientId);
|
|
40201
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
40202
|
+
url.searchParams.set("scope", host.scope);
|
|
40203
|
+
url.searchParams.set("state", state);
|
|
40204
|
+
return url.toString();
|
|
40205
|
+
}
|
|
40206
|
+
async function readJson2(res) {
|
|
40207
|
+
const text6 = await res.text();
|
|
40208
|
+
if (!text6) return {};
|
|
40209
|
+
try {
|
|
40210
|
+
return JSON.parse(text6);
|
|
40211
|
+
} catch {
|
|
40212
|
+
throw new Error("Sign-in provider returned a non-JSON response.");
|
|
40213
|
+
}
|
|
40214
|
+
}
|
|
40215
|
+
function accessToken(body) {
|
|
40216
|
+
const token = body.access_token;
|
|
40217
|
+
if (typeof token !== "string" || !token.trim()) {
|
|
40218
|
+
const message = body.error_description;
|
|
40219
|
+
throw new Error(typeof message === "string" && message.trim() ? message : "Sign-in provider rejected the code.");
|
|
40220
|
+
}
|
|
40221
|
+
return token.trim();
|
|
40222
|
+
}
|
|
40223
|
+
async function fetchGithubIdentity(client, code, redirectUri, fetchImpl) {
|
|
40224
|
+
const tokenRes = await fetchImpl("https://github.com/login/oauth/access_token", {
|
|
40225
|
+
method: "POST",
|
|
40226
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
40227
|
+
body: JSON.stringify({
|
|
40228
|
+
client_id: client.clientId,
|
|
40229
|
+
client_secret: client.clientSecret,
|
|
40230
|
+
code,
|
|
40231
|
+
redirect_uri: redirectUri
|
|
40232
|
+
})
|
|
40233
|
+
});
|
|
40234
|
+
const token = accessToken(await readJson2(tokenRes));
|
|
40235
|
+
const userRes = await fetchImpl("https://api.github.com/user", {
|
|
40236
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "User-Agent": "sideboard-relay" }
|
|
40237
|
+
});
|
|
40238
|
+
const user = await readJson2(userRes);
|
|
40239
|
+
const subject = typeof user.id === "number" ? String(user.id) : "";
|
|
40240
|
+
if (!subject) throw new Error("GitHub did not return an account id.");
|
|
40241
|
+
const emailRes = await fetchImpl("https://api.github.com/user/emails", {
|
|
40242
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "User-Agent": "sideboard-relay" }
|
|
40243
|
+
});
|
|
40244
|
+
const emails = await readJson2(emailRes);
|
|
40245
|
+
const list = Array.isArray(emails) ? emails : [];
|
|
40246
|
+
const primary = list.find((row) => row.primary === true && row.verified === true);
|
|
40247
|
+
const anyVerified = list.find((row) => row.verified === true);
|
|
40248
|
+
const chosen = primary ?? anyVerified;
|
|
40249
|
+
const email = chosen && typeof chosen.email === "string" ? chosen.email : null;
|
|
40250
|
+
return { issuer: GITHUB_ACCOUNT_ISSUER, subject, email, emailVerified: Boolean(email) };
|
|
40251
|
+
}
|
|
40252
|
+
async function fetchRelayIdentity(provider, client, code, redirectUri, fetchImpl) {
|
|
40253
|
+
return GIT_HOST_AUTH[provider].fetchIdentity(client, code, redirectUri, fetchImpl);
|
|
40254
|
+
}
|
|
40255
|
+
async function readBody(req) {
|
|
40256
|
+
const chunks = [];
|
|
40257
|
+
let size = 0;
|
|
40258
|
+
for await (const chunk of req) {
|
|
40259
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
40260
|
+
size += buf.length;
|
|
40261
|
+
if (size > 8192) throw new Error("body too large");
|
|
40262
|
+
chunks.push(buf);
|
|
40263
|
+
}
|
|
40264
|
+
const text6 = Buffer.concat(chunks).toString("utf8").trim();
|
|
40265
|
+
if (!text6) return {};
|
|
40266
|
+
return JSON.parse(text6);
|
|
40267
|
+
}
|
|
40268
|
+
function sendJson2(res, status, body) {
|
|
40269
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
40270
|
+
res.end(JSON.stringify(body));
|
|
40271
|
+
}
|
|
40272
|
+
function sendHtml(res, status, title, body) {
|
|
40273
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
40274
|
+
res.end(`<!DOCTYPE html><html><head><meta charset="utf-8"><title>${escapeHtml2(title)}</title></head><body>${body}</body></html>`);
|
|
40275
|
+
}
|
|
40276
|
+
async function handleRelayAccountHttp(opts) {
|
|
40277
|
+
const reqUrl = opts.req.url || "/";
|
|
40278
|
+
const path2 = reqUrl.split("?")[0];
|
|
40279
|
+
const method = opts.req.method || "GET";
|
|
40280
|
+
const origin = opts.publicOrigin.replace(/\/+$/, "");
|
|
40281
|
+
const redirectUri = `${origin}${REMOTE_OAUTH_CALLBACK_PATH}`;
|
|
40282
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
40283
|
+
if (method === "POST" && path2 === REMOTE_LOGIN_PATH) {
|
|
40284
|
+
let body;
|
|
40285
|
+
try {
|
|
40286
|
+
body = await readBody(opts.req);
|
|
40287
|
+
} catch {
|
|
40288
|
+
sendJson2(opts.res, 400, { ok: false, error: "invalid login request" });
|
|
40289
|
+
return true;
|
|
40290
|
+
}
|
|
40291
|
+
const provider = providerOf(typeof body.provider === "string" ? body.provider : "");
|
|
40292
|
+
const state = typeof body.state === "string" ? body.state.trim() : "";
|
|
40293
|
+
if (!provider || !STATE_RE.test(state)) {
|
|
40294
|
+
sendJson2(opts.res, 400, { ok: false, error: "invalid login request" });
|
|
40295
|
+
return true;
|
|
40296
|
+
}
|
|
40297
|
+
const client = opts.oauth[provider];
|
|
40298
|
+
if (!client?.clientId || !client.clientSecret) {
|
|
40299
|
+
sendJson2(opts.res, 503, {
|
|
40300
|
+
ok: false,
|
|
40301
|
+
error: `${GIT_HOST_AUTH[provider].label} sign-in is not configured on this relay.`
|
|
40302
|
+
});
|
|
40303
|
+
return true;
|
|
40304
|
+
}
|
|
40305
|
+
let accountId = null;
|
|
40306
|
+
const linkToken = typeof body.accountToken === "string" ? body.accountToken.trim() : "";
|
|
40307
|
+
if (linkToken) {
|
|
40308
|
+
accountId = opts.store.verifyCredential(linkToken);
|
|
40309
|
+
if (!accountId) {
|
|
40310
|
+
sendJson2(opts.res, 401, { ok: false, error: "sign in required" });
|
|
40311
|
+
return true;
|
|
40312
|
+
}
|
|
40313
|
+
}
|
|
40314
|
+
opts.sessions.begin(state, provider, accountId);
|
|
40315
|
+
sendJson2(opts.res, 200, { ok: true, url: relayAuthorizeUrl(provider, client, redirectUri, state) });
|
|
40316
|
+
return true;
|
|
40317
|
+
}
|
|
40318
|
+
if (method === "GET" && path2 === REMOTE_OAUTH_CALLBACK_PATH) {
|
|
40319
|
+
const url = new URL(reqUrl, origin);
|
|
40320
|
+
const state = url.searchParams.get("state")?.trim() ?? "";
|
|
40321
|
+
const code = url.searchParams.get("code")?.trim() ?? "";
|
|
40322
|
+
const providerError = url.searchParams.get("error")?.trim() ?? "";
|
|
40323
|
+
const pending = STATE_RE.test(state) ? opts.sessions.takeLogin(state) : null;
|
|
40324
|
+
if (!pending) {
|
|
40325
|
+
sendHtml(opts.res, 400, "Sideboard", "<h1>Sign-in expired</h1><p>Return to Sideboard and try again.</p>");
|
|
40326
|
+
return true;
|
|
40327
|
+
}
|
|
40328
|
+
if (providerError || !code) {
|
|
40329
|
+
const message = providerError || "Sign-in was cancelled.";
|
|
40330
|
+
opts.sessions.putResult(state, { ok: false, error: message });
|
|
40331
|
+
sendHtml(opts.res, 400, "Sideboard", `<h1>Could not sign in</h1><p>${escapeHtml2(message)}</p>`);
|
|
40332
|
+
return true;
|
|
40333
|
+
}
|
|
40334
|
+
const client = opts.oauth[pending.provider];
|
|
40335
|
+
if (!client) {
|
|
40336
|
+
opts.sessions.putResult(state, { ok: false, error: "Sign-in is not configured on this relay." });
|
|
40337
|
+
sendHtml(opts.res, 503, "Sideboard", "<h1>Sign-in is not configured</h1>");
|
|
40338
|
+
return true;
|
|
40339
|
+
}
|
|
40340
|
+
try {
|
|
40341
|
+
const identity = await fetchRelayIdentity(pending.provider, client, code, redirectUri, fetchImpl);
|
|
40342
|
+
const signed = opts.store.signIn(identity, pending.accountId);
|
|
40343
|
+
opts.sessions.putResult(state, {
|
|
40344
|
+
ok: true,
|
|
40345
|
+
credential: signed.credential,
|
|
40346
|
+
email: signed.email,
|
|
40347
|
+
accountId: signed.accountId
|
|
40348
|
+
});
|
|
40349
|
+
sendHtml(
|
|
40350
|
+
opts.res,
|
|
40351
|
+
200,
|
|
40352
|
+
"Sideboard",
|
|
40353
|
+
"<h1>Signed in</h1><p>Return to Sideboard. This window can close.</p>"
|
|
40354
|
+
);
|
|
40355
|
+
} catch (err) {
|
|
40356
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
40357
|
+
opts.sessions.putResult(state, { ok: false, error: message });
|
|
40358
|
+
sendHtml(opts.res, 400, "Sideboard", `<h1>Could not sign in</h1><p>${escapeHtml2(message)}</p>`);
|
|
40359
|
+
}
|
|
40360
|
+
return true;
|
|
40361
|
+
}
|
|
40362
|
+
if (method === "GET" && path2 === REMOTE_OAUTH_RESULT_PATH) {
|
|
40363
|
+
const url = new URL(reqUrl, origin);
|
|
40364
|
+
const state = url.searchParams.get("state")?.trim() ?? "";
|
|
40365
|
+
if (!STATE_RE.test(state)) {
|
|
40366
|
+
sendJson2(opts.res, 400, { ok: false, error: "invalid login request" });
|
|
40367
|
+
return true;
|
|
40368
|
+
}
|
|
40369
|
+
const result = opts.sessions.takeResult(state);
|
|
40370
|
+
if (result === void 0 || result === null) {
|
|
40371
|
+
sendJson2(opts.res, 404, { ok: false, error: "pending" });
|
|
40372
|
+
return true;
|
|
40373
|
+
}
|
|
40374
|
+
if (!result.ok) {
|
|
40375
|
+
sendJson2(opts.res, 400, { ok: false, error: result.error });
|
|
40376
|
+
return true;
|
|
40377
|
+
}
|
|
40378
|
+
sendJson2(opts.res, 200, {
|
|
40379
|
+
ok: true,
|
|
40380
|
+
credential: result.credential,
|
|
40381
|
+
email: result.email,
|
|
40382
|
+
accountId: result.accountId
|
|
40383
|
+
});
|
|
40384
|
+
return true;
|
|
40385
|
+
}
|
|
40386
|
+
if (method === "POST" && path2 === REMOTE_LOGOUT_PATH) {
|
|
40387
|
+
let body;
|
|
40388
|
+
try {
|
|
40389
|
+
body = await readBody(opts.req);
|
|
40390
|
+
} catch {
|
|
40391
|
+
sendJson2(opts.res, 400, { ok: false, error: "invalid logout request" });
|
|
40392
|
+
return true;
|
|
40393
|
+
}
|
|
40394
|
+
const token = typeof body.accountToken === "string" ? body.accountToken : "";
|
|
40395
|
+
opts.store.logout(token);
|
|
40396
|
+
sendJson2(opts.res, 200, { ok: true });
|
|
40397
|
+
return true;
|
|
40398
|
+
}
|
|
40399
|
+
return false;
|
|
40400
|
+
}
|
|
40401
|
+
|
|
40402
|
+
// src/relay/account-login.ts
|
|
40403
|
+
var RemoteAccountLoginCancelled = class extends Error {
|
|
40404
|
+
constructor() {
|
|
40405
|
+
super("Sign-in cancelled");
|
|
40406
|
+
this.name = "RemoteAccountLoginCancelled";
|
|
40407
|
+
}
|
|
40408
|
+
};
|
|
40409
|
+
function isRemoteAccountLoginCancelled(err) {
|
|
40410
|
+
return err instanceof RemoteAccountLoginCancelled;
|
|
40411
|
+
}
|
|
40412
|
+
function remoteAccountHttpOrigin(wsUrl = remoteRelayUrl()) {
|
|
40413
|
+
const url = new URL(wsUrl);
|
|
40414
|
+
url.protocol = url.protocol === "wss:" ? "https:" : "http:";
|
|
40415
|
+
url.pathname = "";
|
|
40416
|
+
url.search = "";
|
|
40417
|
+
url.hash = "";
|
|
40418
|
+
return url.origin;
|
|
40419
|
+
}
|
|
40420
|
+
function sleep2(ms, signal) {
|
|
40421
|
+
return new Promise((resolve2, reject) => {
|
|
40422
|
+
if (signal?.aborted) {
|
|
40423
|
+
reject(new RemoteAccountLoginCancelled());
|
|
40424
|
+
return;
|
|
40425
|
+
}
|
|
40426
|
+
const timer = setTimeout(() => {
|
|
40427
|
+
signal?.removeEventListener("abort", onAbort);
|
|
40428
|
+
resolve2();
|
|
40429
|
+
}, ms);
|
|
40430
|
+
const onAbort = () => {
|
|
40431
|
+
clearTimeout(timer);
|
|
40432
|
+
reject(new RemoteAccountLoginCancelled());
|
|
40433
|
+
};
|
|
40434
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
40435
|
+
});
|
|
40436
|
+
}
|
|
40437
|
+
async function startRemoteAccountLogin(opts) {
|
|
40438
|
+
if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
|
|
40439
|
+
const origin = (opts.origin ?? remoteAccountHttpOrigin()).replace(/\/+$/, "");
|
|
40440
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
40441
|
+
const state = (0, import_node_crypto27.randomBytes)(16).toString("hex");
|
|
40442
|
+
const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
|
|
40443
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 400;
|
|
40444
|
+
const linkToken = opts.linkToken?.trim() ?? "";
|
|
40445
|
+
const started = await fetchImpl(`${origin}${REMOTE_LOGIN_PATH}`, {
|
|
40446
|
+
method: "POST",
|
|
40447
|
+
headers: { "Content-Type": "application/json" },
|
|
40448
|
+
body: JSON.stringify({
|
|
40449
|
+
provider: opts.provider,
|
|
40450
|
+
state,
|
|
40451
|
+
...linkToken ? { accountToken: linkToken } : {}
|
|
40452
|
+
})
|
|
40453
|
+
});
|
|
40454
|
+
const startBody = await started.json();
|
|
40455
|
+
if (!started.ok || !startBody.url) {
|
|
40456
|
+
throw new Error(startBody.error || "Could not start sign-in.");
|
|
40457
|
+
}
|
|
40458
|
+
await Promise.resolve(opts.openUrl?.(startBody.url));
|
|
40459
|
+
const deadline = Date.now() + timeoutMs;
|
|
40460
|
+
const resultUrl = `${origin}${REMOTE_OAUTH_RESULT_PATH}?state=${encodeURIComponent(state)}`;
|
|
40461
|
+
while (Date.now() < deadline) {
|
|
40462
|
+
if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
|
|
40463
|
+
let res;
|
|
40464
|
+
try {
|
|
40465
|
+
res = await fetchImpl(resultUrl);
|
|
40466
|
+
} catch {
|
|
40467
|
+
if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
|
|
40468
|
+
await sleep2(pollIntervalMs, opts.signal);
|
|
40469
|
+
continue;
|
|
40470
|
+
}
|
|
40471
|
+
let data;
|
|
40472
|
+
try {
|
|
40473
|
+
data = await res.json();
|
|
40474
|
+
} catch {
|
|
40475
|
+
await sleep2(pollIntervalMs, opts.signal);
|
|
40476
|
+
continue;
|
|
40477
|
+
}
|
|
40478
|
+
if (data.ok && data.credential && data.accountId) {
|
|
40479
|
+
return {
|
|
40480
|
+
credential: data.credential,
|
|
40481
|
+
email: data.email ?? null,
|
|
40482
|
+
accountId: data.accountId
|
|
40483
|
+
};
|
|
40484
|
+
}
|
|
40485
|
+
if (data.error && data.error !== "pending") throw new Error(data.error);
|
|
40486
|
+
await sleep2(pollIntervalMs, opts.signal);
|
|
40487
|
+
}
|
|
40488
|
+
throw new Error("Sign-in timed out.");
|
|
40489
|
+
}
|
|
40490
|
+
async function disconnectRemoteAccount(opts) {
|
|
40491
|
+
const origin = (opts.origin ?? remoteAccountHttpOrigin()).replace(/\/+$/, "");
|
|
40492
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
40493
|
+
await fetchImpl(`${origin}${REMOTE_LOGOUT_PATH}`, {
|
|
40494
|
+
method: "POST",
|
|
40495
|
+
headers: { "Content-Type": "application/json" },
|
|
40496
|
+
body: JSON.stringify({ accountToken: opts.accountToken })
|
|
40497
|
+
});
|
|
40498
|
+
}
|
|
40499
|
+
|
|
39053
40500
|
// src/slack/app-home.ts
|
|
39054
40501
|
var SLACK_APP_HOME_DOWNLOAD = "https://sideboard-downloads.t3.tigrisfiles.io/Sideboard-latest-arm64.dmg";
|
|
39055
40502
|
var SLACK_APP_HOME_LANDING = "https://www.sideboard.cloud/slack/";
|
|
@@ -39431,9 +40878,9 @@ var import_node_http5 = require("http");
|
|
|
39431
40878
|
var import_ws6 = require("ws");
|
|
39432
40879
|
|
|
39433
40880
|
// src/relay/static.ts
|
|
39434
|
-
var
|
|
40881
|
+
var import_node_fs71 = require("fs");
|
|
39435
40882
|
var import_promises = require("fs/promises");
|
|
39436
|
-
var
|
|
40883
|
+
var import_node_path69 = __toESM(require("path"), 1);
|
|
39437
40884
|
var TYPES = {
|
|
39438
40885
|
".css": "text/css; charset=utf-8",
|
|
39439
40886
|
".html": "text/html; charset=utf-8",
|
|
@@ -39464,9 +40911,9 @@ function resolveStaticPath(root, requestUrl) {
|
|
|
39464
40911
|
return null;
|
|
39465
40912
|
}
|
|
39466
40913
|
if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
|
|
39467
|
-
const rootResolved =
|
|
39468
|
-
const candidate =
|
|
39469
|
-
if (candidate !== rootResolved && !candidate.startsWith(rootResolved +
|
|
40914
|
+
const rootResolved = import_node_path69.default.resolve(root);
|
|
40915
|
+
const candidate = import_node_path69.default.resolve(rootResolved, `.${pathname}`);
|
|
40916
|
+
if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path69.default.sep)) {
|
|
39470
40917
|
return null;
|
|
39471
40918
|
}
|
|
39472
40919
|
return candidate;
|
|
@@ -39480,7 +40927,7 @@ async function fileSize(file) {
|
|
|
39480
40927
|
}
|
|
39481
40928
|
}
|
|
39482
40929
|
function sendFile(req, res, file, size) {
|
|
39483
|
-
const ext =
|
|
40930
|
+
const ext = import_node_path69.default.extname(file).toLowerCase();
|
|
39484
40931
|
res.writeHead(200, {
|
|
39485
40932
|
"Content-Type": TYPES[ext] ?? "application/octet-stream",
|
|
39486
40933
|
"Content-Length": size,
|
|
@@ -39490,7 +40937,7 @@ function sendFile(req, res, file, size) {
|
|
|
39490
40937
|
res.end();
|
|
39491
40938
|
return true;
|
|
39492
40939
|
}
|
|
39493
|
-
(0,
|
|
40940
|
+
(0, import_node_fs71.createReadStream)(file).pipe(res);
|
|
39494
40941
|
return true;
|
|
39495
40942
|
}
|
|
39496
40943
|
async function tryServeStatic(req, res, root) {
|
|
@@ -39499,7 +40946,7 @@ async function tryServeStatic(req, res, root) {
|
|
|
39499
40946
|
if (!candidate) return false;
|
|
39500
40947
|
const direct = await fileSize(candidate);
|
|
39501
40948
|
if (direct != null) return sendFile(req, res, candidate, direct);
|
|
39502
|
-
const asIndex =
|
|
40949
|
+
const asIndex = import_node_path69.default.join(candidate, "index.html");
|
|
39503
40950
|
const indexSize = await fileSize(asIndex);
|
|
39504
40951
|
if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
|
|
39505
40952
|
return false;
|
|
@@ -39507,14 +40954,19 @@ async function tryServeStatic(req, res, root) {
|
|
|
39507
40954
|
|
|
39508
40955
|
// src/relay/server.ts
|
|
39509
40956
|
init_abletime_oauth();
|
|
39510
|
-
function
|
|
40957
|
+
function sendHtml2(res, status, title, body) {
|
|
39511
40958
|
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
39512
40959
|
res.end(slackOAuthHtmlPage(title, body));
|
|
39513
40960
|
}
|
|
39514
|
-
function
|
|
40961
|
+
function sendJson3(res, status, body) {
|
|
39515
40962
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
39516
40963
|
res.end(JSON.stringify(body));
|
|
39517
40964
|
}
|
|
40965
|
+
function relayClientIp(req) {
|
|
40966
|
+
const fly = req.headers["fly-client-ip"];
|
|
40967
|
+
if (typeof fly === "string" && fly.trim()) return fly.trim();
|
|
40968
|
+
return req.socket.remoteAddress ?? "unknown";
|
|
40969
|
+
}
|
|
39518
40970
|
function startRelaySocketPing(ws, intervalMs) {
|
|
39519
40971
|
const ping = setInterval(() => {
|
|
39520
40972
|
if (ws.readyState !== ws.OPEN) {
|
|
@@ -39542,11 +40994,26 @@ async function startRelayServer(opts) {
|
|
|
39542
40994
|
const clientSecret = (opts.clientSecret ?? process.env.SIDEBOARD_SLACK_CLIENT_SECRET ?? "").trim();
|
|
39543
40995
|
const redirectUri = (opts.oauthRedirectUri ?? slackOAuthRelayRedirectUri()).trim();
|
|
39544
40996
|
const pending = new SlackOAuthPendingStore();
|
|
40997
|
+
let boundPort = 0;
|
|
39545
40998
|
const hub = opts.hub ?? new SlackRelayHub({
|
|
39546
40999
|
fetchImpl: opts.fetchImpl ?? fetch,
|
|
39547
41000
|
onLog: log
|
|
39548
41001
|
});
|
|
39549
|
-
const
|
|
41002
|
+
const accountsPath = opts.accountsPath?.trim() ?? "";
|
|
41003
|
+
if (accountsPath) assertRelayAccountsDurable(accountsPath);
|
|
41004
|
+
const accountStore = accountsPath ? new RelayAccountStore(accountsPath) : null;
|
|
41005
|
+
const accountSessions = new RelayLoginSessions();
|
|
41006
|
+
const relayOAuth = opts.relayOAuth ?? {};
|
|
41007
|
+
const authMarks = /* @__PURE__ */ new Map();
|
|
41008
|
+
const remoteByIp = /* @__PURE__ */ new Map();
|
|
41009
|
+
let remoteSockets = 0;
|
|
41010
|
+
const remoteHub = new RemoteHub({
|
|
41011
|
+
authorizeHost: accountStore ? (input) => {
|
|
41012
|
+
const decision = accountStore.authorizeHost(input);
|
|
41013
|
+
return decision.ok ? { ok: true } : { ok: false, message: decision.message };
|
|
41014
|
+
} : void 0,
|
|
41015
|
+
onAuthorized: (socket) => authMarks.get(socket)?.()
|
|
41016
|
+
});
|
|
39550
41017
|
const handleCallback = async (reqUrl, res) => {
|
|
39551
41018
|
const url2 = parseSlackOAuthCallbackUrl(reqUrl);
|
|
39552
41019
|
if (!url2) return false;
|
|
@@ -39555,7 +41022,7 @@ async function startRelayServer(opts) {
|
|
|
39555
41022
|
const code = url2.searchParams.get("code")?.trim() ?? "";
|
|
39556
41023
|
if (error) {
|
|
39557
41024
|
if (state) pending.put(state, { ok: false, error: `Slack OAuth: ${error}` });
|
|
39558
|
-
|
|
41025
|
+
sendHtml2(
|
|
39559
41026
|
res,
|
|
39560
41027
|
400,
|
|
39561
41028
|
"Slack",
|
|
@@ -39564,7 +41031,7 @@ async function startRelayServer(opts) {
|
|
|
39564
41031
|
return true;
|
|
39565
41032
|
}
|
|
39566
41033
|
if (!state || !code) {
|
|
39567
|
-
|
|
41034
|
+
sendHtml2(res, 400, "Slack", "<h1>Invalid callback</h1><p>State or code missing.</p>");
|
|
39568
41035
|
return true;
|
|
39569
41036
|
}
|
|
39570
41037
|
if (!clientSecret) {
|
|
@@ -39572,7 +41039,7 @@ async function startRelayServer(opts) {
|
|
|
39572
41039
|
ok: false,
|
|
39573
41040
|
error: "Relay is missing SIDEBOARD_SLACK_CLIENT_SECRET"
|
|
39574
41041
|
});
|
|
39575
|
-
|
|
41042
|
+
sendHtml2(
|
|
39576
41043
|
res,
|
|
39577
41044
|
500,
|
|
39578
41045
|
"Slack",
|
|
@@ -39595,7 +41062,7 @@ async function startRelayServer(opts) {
|
|
|
39595
41062
|
void hub.handleAppHomeOpened(payload.team_id, payload.user_id);
|
|
39596
41063
|
}
|
|
39597
41064
|
}
|
|
39598
|
-
|
|
41065
|
+
sendHtml2(
|
|
39599
41066
|
res,
|
|
39600
41067
|
200,
|
|
39601
41068
|
"Slack connected",
|
|
@@ -39604,7 +41071,7 @@ async function startRelayServer(opts) {
|
|
|
39604
41071
|
} catch (err) {
|
|
39605
41072
|
const message = err instanceof Error ? err.message : String(err);
|
|
39606
41073
|
pending.put(state, { ok: false, error: message });
|
|
39607
|
-
|
|
41074
|
+
sendHtml2(res, 400, "Slack", `<h1>Could not connect Slack</h1><p>${escapeHtml2(message)}</p>`);
|
|
39608
41075
|
}
|
|
39609
41076
|
return true;
|
|
39610
41077
|
};
|
|
@@ -39614,19 +41081,30 @@ async function startRelayServer(opts) {
|
|
|
39614
41081
|
const state = url2.searchParams.get("state")?.trim() ?? "";
|
|
39615
41082
|
const value = pending.take(state);
|
|
39616
41083
|
if (!value) {
|
|
39617
|
-
|
|
41084
|
+
sendJson3(res, 404, { ok: false, error: "pending" });
|
|
39618
41085
|
return true;
|
|
39619
41086
|
}
|
|
39620
41087
|
if (!value.ok) {
|
|
39621
|
-
|
|
41088
|
+
sendJson3(res, 400, { ok: false, error: value.error });
|
|
39622
41089
|
return true;
|
|
39623
41090
|
}
|
|
39624
|
-
|
|
41091
|
+
sendJson3(res, 200, { ok: true, ...value.payload });
|
|
39625
41092
|
return true;
|
|
39626
41093
|
};
|
|
39627
41094
|
const httpServer = (0, import_node_http5.createServer)((req, res) => {
|
|
39628
41095
|
void (async () => {
|
|
39629
41096
|
const reqUrl = req.url || "/";
|
|
41097
|
+
if (accountStore && await handleRelayAccountHttp({
|
|
41098
|
+
req,
|
|
41099
|
+
res,
|
|
41100
|
+
store: accountStore,
|
|
41101
|
+
oauth: relayOAuth,
|
|
41102
|
+
sessions: accountSessions,
|
|
41103
|
+
publicOrigin: opts.relayPublicOrigin?.trim() || process.env.SIDEBOARD_RELAY_PUBLIC_ORIGIN?.trim() || `http://127.0.0.1:${boundPort}`,
|
|
41104
|
+
fetchImpl: opts.fetchImpl
|
|
41105
|
+
})) {
|
|
41106
|
+
return;
|
|
41107
|
+
}
|
|
39630
41108
|
if (await handleCallback(reqUrl, res)) return;
|
|
39631
41109
|
if (handleResult(reqUrl, res)) return;
|
|
39632
41110
|
const ableTimeBounce = ableTimeOAuthLocalBounceUrl(reqUrl);
|
|
@@ -39640,7 +41118,7 @@ async function startRelayServer(opts) {
|
|
|
39640
41118
|
return;
|
|
39641
41119
|
}
|
|
39642
41120
|
if (req.url === "/health") {
|
|
39643
|
-
|
|
41121
|
+
sendJson3(res, 200, {
|
|
39644
41122
|
ok: true,
|
|
39645
41123
|
service: "sideboard-relay",
|
|
39646
41124
|
sessions: hub.listSessions().length,
|
|
@@ -39659,7 +41137,7 @@ async function startRelayServer(opts) {
|
|
|
39659
41137
|
return;
|
|
39660
41138
|
}
|
|
39661
41139
|
if (req.url === "/") {
|
|
39662
|
-
|
|
41140
|
+
sendJson3(res, 200, {
|
|
39663
41141
|
ok: true,
|
|
39664
41142
|
service: "sideboard-relay",
|
|
39665
41143
|
sessions: hub.listSessions().length,
|
|
@@ -39686,6 +41164,14 @@ async function startRelayServer(opts) {
|
|
|
39686
41164
|
socket.destroy();
|
|
39687
41165
|
return;
|
|
39688
41166
|
}
|
|
41167
|
+
if (target === remoteWss) {
|
|
41168
|
+
const ip = relayClientIp(req);
|
|
41169
|
+
const count = remoteByIp.get(ip) ?? 0;
|
|
41170
|
+
if (count >= 20 || remoteSockets >= 4e3) {
|
|
41171
|
+
socket.destroy();
|
|
41172
|
+
return;
|
|
41173
|
+
}
|
|
41174
|
+
}
|
|
39689
41175
|
target.handleUpgrade(req, socket, head, (ws) => {
|
|
39690
41176
|
target.emit("connection", ws, req);
|
|
39691
41177
|
});
|
|
@@ -39720,7 +41206,10 @@ async function startRelayServer(opts) {
|
|
|
39720
41206
|
hub.detachSocket(socket);
|
|
39721
41207
|
});
|
|
39722
41208
|
});
|
|
39723
|
-
remoteWss.on("connection", (ws) => {
|
|
41209
|
+
remoteWss.on("connection", (ws, req) => {
|
|
41210
|
+
const ip = relayClientIp(req);
|
|
41211
|
+
remoteByIp.set(ip, (remoteByIp.get(ip) ?? 0) + 1);
|
|
41212
|
+
remoteSockets += 1;
|
|
39724
41213
|
const socket = {
|
|
39725
41214
|
send: (data) => {
|
|
39726
41215
|
if (ws.readyState === ws.OPEN) ws.send(data);
|
|
@@ -39732,14 +41221,35 @@ async function startRelayServer(opts) {
|
|
|
39732
41221
|
}
|
|
39733
41222
|
}
|
|
39734
41223
|
};
|
|
41224
|
+
let released = false;
|
|
41225
|
+
const authTimer = accountStore ? setTimeout(() => {
|
|
41226
|
+
try {
|
|
41227
|
+
ws.close(4001, "sign in required");
|
|
41228
|
+
} catch {
|
|
41229
|
+
}
|
|
41230
|
+
}, 12e4) : null;
|
|
41231
|
+
authMarks.set(socket, () => {
|
|
41232
|
+
if (authTimer) clearTimeout(authTimer);
|
|
41233
|
+
});
|
|
41234
|
+
const release = () => {
|
|
41235
|
+
if (released) return;
|
|
41236
|
+
released = true;
|
|
41237
|
+
if (authTimer) clearTimeout(authTimer);
|
|
41238
|
+
authMarks.delete(socket);
|
|
41239
|
+
remoteSockets -= 1;
|
|
41240
|
+
const next = (remoteByIp.get(ip) ?? 1) - 1;
|
|
41241
|
+
if (next <= 0) remoteByIp.delete(ip);
|
|
41242
|
+
else remoteByIp.set(ip, next);
|
|
41243
|
+
remoteHub.detachSocket(socket);
|
|
41244
|
+
};
|
|
39735
41245
|
startRelaySocketPing(ws, REMOTE_PING_INTERVAL_MS);
|
|
39736
41246
|
log("remote client connected");
|
|
39737
41247
|
ws.on("message", (data) => {
|
|
39738
41248
|
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
39739
41249
|
remoteHub.handleClientMessage(socket, raw);
|
|
39740
41250
|
});
|
|
39741
|
-
ws.on("close",
|
|
39742
|
-
ws.on("error",
|
|
41251
|
+
ws.on("close", release);
|
|
41252
|
+
ws.on("error", release);
|
|
39743
41253
|
});
|
|
39744
41254
|
const port = opts.port ?? 0;
|
|
39745
41255
|
const host = opts.host ?? "0.0.0.0";
|
|
@@ -39748,7 +41258,7 @@ async function startRelayServer(opts) {
|
|
|
39748
41258
|
httpServer.listen(port, host, () => resolve2());
|
|
39749
41259
|
});
|
|
39750
41260
|
const address = httpServer.address();
|
|
39751
|
-
|
|
41261
|
+
boundPort = typeof address === "object" && address ? address.port : typeof port === "number" ? port : 0;
|
|
39752
41262
|
const url = `ws://${host === "0.0.0.0" ? "127.0.0.1" : host}:${boundPort}${SLACK_RELAY_DESKTOP_PATH}`;
|
|
39753
41263
|
log(`relay listening on ${url}`);
|
|
39754
41264
|
const ac = new AbortController();
|
|
@@ -39791,13 +41301,14 @@ async function startRelayServer(opts) {
|
|
|
39791
41301
|
httpServer.close((err) => err ? reject(err) : resolve2());
|
|
39792
41302
|
});
|
|
39793
41303
|
await socketModeDone.catch(() => void 0);
|
|
41304
|
+
accountStore?.close();
|
|
39794
41305
|
}
|
|
39795
41306
|
};
|
|
39796
41307
|
}
|
|
39797
41308
|
|
|
39798
41309
|
// src/slack/outbound-watch.ts
|
|
39799
|
-
var
|
|
39800
|
-
var
|
|
41310
|
+
var import_node_fs72 = require("fs");
|
|
41311
|
+
var import_node_path70 = require("path");
|
|
39801
41312
|
init_paths();
|
|
39802
41313
|
init_private_file();
|
|
39803
41314
|
init_secure_file();
|
|
@@ -39810,7 +41321,7 @@ var POLL_INTERVAL_MS = 12e3;
|
|
|
39810
41321
|
var lastPollMs = 0;
|
|
39811
41322
|
var nameCache = /* @__PURE__ */ new Map();
|
|
39812
41323
|
function storePath4() {
|
|
39813
|
-
return (0,
|
|
41324
|
+
return (0, import_node_path70.join)(appDataDir(), "slack-outbound-watch.json");
|
|
39814
41325
|
}
|
|
39815
41326
|
function watchId(teamId, channelId, ts) {
|
|
39816
41327
|
return `${teamId}:${channelId}:${ts}`;
|
|
@@ -39823,9 +41334,9 @@ function tsNewer(a, b) {
|
|
|
39823
41334
|
}
|
|
39824
41335
|
function readStore4() {
|
|
39825
41336
|
const path2 = storePath4();
|
|
39826
|
-
if (!(0,
|
|
41337
|
+
if (!(0, import_node_fs72.existsSync)(path2)) return [];
|
|
39827
41338
|
try {
|
|
39828
|
-
const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse((0,
|
|
41339
|
+
const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse((0, import_node_fs72.readFileSync)(path2, "utf8"));
|
|
39829
41340
|
return Array.isArray(parsed?.watches) ? parsed.watches : [];
|
|
39830
41341
|
} catch {
|
|
39831
41342
|
return [];
|
|
@@ -40470,6 +41981,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
40470
41981
|
disconnectLinear,
|
|
40471
41982
|
disconnectLinearConnection,
|
|
40472
41983
|
disconnectOptionalService,
|
|
41984
|
+
disconnectRemoteAccount,
|
|
40473
41985
|
disconnectSlackWorkspace,
|
|
40474
41986
|
discoverSkills,
|
|
40475
41987
|
downloadAbleTimeAttachment,
|
|
@@ -40726,6 +42238,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
40726
42238
|
isPrNotMergeableError,
|
|
40727
42239
|
isPresentPlanToolName,
|
|
40728
42240
|
isPrimaryCheckoutThread,
|
|
42241
|
+
isRemoteAccountLoginCancelled,
|
|
40729
42242
|
isRemoteStopCommand,
|
|
40730
42243
|
isSelfWrittenRecord,
|
|
40731
42244
|
isSessionQuotaLimit,
|
|
@@ -41087,6 +42600,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
41087
42600
|
startMcpServer,
|
|
41088
42601
|
startOrchestration,
|
|
41089
42602
|
startRelayServer,
|
|
42603
|
+
startRemoteAccountLogin,
|
|
41090
42604
|
startSlackOAuth,
|
|
41091
42605
|
statWorktreePath,
|
|
41092
42606
|
stopAgentRpcServer,
|