skydive-cli 0.5.0-beta.13 → 0.5.0-beta.15
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/js/bin.mjs +13 -13
- package/dist/js/{boot-C4OHQUCg.mjs → boot-DZKl8JNI.mjs} +153 -51
- package/dist/js/{client-DabRpc_T.mjs → client-DbqRBquD.mjs} +61 -21
- package/dist/js/client-hH2PJL8y.mjs +5 -0
- package/dist/js/{daemon-BfqJZ_M4.mjs → daemon-Djei8xaW.mjs} +2 -2
- package/dist/js/daemon-client-Bemq-pLd.mjs +7 -0
- package/dist/js/{daemon-client-BVAlyN7x.mjs → daemon-client-N8rvGusv.mjs} +1 -1
- package/dist/js/{daemon-ShOaDbZe.mjs → daemon-eb9dm6Ih.mjs} +30 -4
- package/dist/js/{print-share-CKLPmsg0.mjs → print-share-BH9gvCls.mjs} +3 -1
- package/dist/js/{profiler-0_q2DHpq.mjs → profiler-B97UpfHm.mjs} +1 -1
- package/package.json +1 -1
- package/dist/js/client-BuU34IVE.mjs +0 -5
- package/dist/js/daemon-client-D_MeDMpn.mjs +0 -7
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-
|
|
2
|
+
import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-B97UpfHm.mjs";
|
|
3
3
|
import { A as getStoredApiKeyId, B as saveConfig, C as getDefaultAgent, E as getPromptHistoryPath, F as resolveChatAuth, I as resolveConfig, L as resolveManagementAuth, M as getUpdateCheckDisabled, P as resolveAppUrl, R as resolveSession, S as getConfigPath, T as getPreference, U as setLastSeenVersion, V as saveSession, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getStoredApiKeyWorkspaceName, k as getShareMachineDefault, v as DEFAULT_API_URL, w as getLastSeenVersion, x as deleteConfig } from "./print-riiY5Gly.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
6
6
|
import { t as createRestClient } from "./rest-CLrHVkxh.mjs";
|
|
7
7
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
8
|
-
import { r as resolveMachineIdentity } from "./client-
|
|
9
|
-
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-
|
|
8
|
+
import { r as resolveMachineIdentity } from "./client-DbqRBquD.mjs";
|
|
9
|
+
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-eb9dm6Ih.mjs";
|
|
10
10
|
import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-DG5W6iwx.mjs";
|
|
11
11
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
12
12
|
import { hideBin } from "yargs/helpers";
|
|
@@ -1517,7 +1517,7 @@ const importCommand = {
|
|
|
1517
1517
|
process.exit(1);
|
|
1518
1518
|
}
|
|
1519
1519
|
}
|
|
1520
|
-
const { runChat } = await import("./boot-
|
|
1520
|
+
const { runChat } = await import("./boot-DZKl8JNI.mjs");
|
|
1521
1521
|
await runChat({
|
|
1522
1522
|
appUrl,
|
|
1523
1523
|
sessionToken: session.value.sessionToken,
|
|
@@ -1545,7 +1545,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1545
1545
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1546
1546
|
process.exit(1);
|
|
1547
1547
|
}
|
|
1548
|
-
const { connectMachineShare } = await import("./print-share-
|
|
1548
|
+
const { connectMachineShare } = await import("./print-share-BH9gvCls.mjs");
|
|
1549
1549
|
const machineShare = await connectMachineShare({
|
|
1550
1550
|
appUrl,
|
|
1551
1551
|
sessionToken: session.value.sessionToken,
|
|
@@ -1930,7 +1930,7 @@ const chatCommand = {
|
|
|
1930
1930
|
sessionToken: auth.value.token,
|
|
1931
1931
|
agentSelector: argv.agent ?? null
|
|
1932
1932
|
});
|
|
1933
|
-
const { runChat } = await import("./boot-
|
|
1933
|
+
const { runChat } = await import("./boot-DZKl8JNI.mjs");
|
|
1934
1934
|
await runChat({
|
|
1935
1935
|
appUrl: auth.value.appUrl,
|
|
1936
1936
|
sessionToken: auth.value.token,
|
|
@@ -2023,7 +2023,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2023
2023
|
process.exit(1);
|
|
2024
2024
|
}
|
|
2025
2025
|
} else {
|
|
2026
|
-
const { connectMachineShare } = await import("./print-share-
|
|
2026
|
+
const { connectMachineShare } = await import("./print-share-BH9gvCls.mjs");
|
|
2027
2027
|
machineShare = await connectMachineShare({
|
|
2028
2028
|
appUrl: auth.value.appUrl,
|
|
2029
2029
|
sessionToken: auth.value.token,
|
|
@@ -2290,7 +2290,7 @@ const switchCommand = {
|
|
|
2290
2290
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
2291
2291
|
process.exit(1);
|
|
2292
2292
|
}
|
|
2293
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2293
|
+
const { runWorkspacePicker } = await import("./boot-DZKl8JNI.mjs");
|
|
2294
2294
|
await runWorkspacePicker(session);
|
|
2295
2295
|
return;
|
|
2296
2296
|
}
|
|
@@ -2339,7 +2339,7 @@ const workspaceCommand = {
|
|
|
2339
2339
|
*/
|
|
2340
2340
|
async function resolveGrantTarget(argv) {
|
|
2341
2341
|
const session = requireSession(argv);
|
|
2342
|
-
const [{ devices, agents }, identity] = await Promise.all([fetchPortalDevices(session), resolveMachineIdentity()]);
|
|
2342
|
+
const [{ devices, agents }, identity] = await Promise.all([fetchPortalDevices(session), resolveMachineIdentity(null)]);
|
|
2343
2343
|
return {
|
|
2344
2344
|
session,
|
|
2345
2345
|
agent: resolveAgent(agents, argv.agent),
|
|
@@ -2368,8 +2368,8 @@ const openCommand = {
|
|
|
2368
2368
|
const session = requireSession(argv);
|
|
2369
2369
|
const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2370
2370
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2371
|
-
const { machineName } = await resolveMachineIdentity();
|
|
2372
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2371
|
+
const { machineName } = await resolveMachineIdentity(null);
|
|
2372
|
+
const { PortalDaemonClient } = await import("./daemon-client-Bemq-pLd.mjs");
|
|
2373
2373
|
let lastLine = "";
|
|
2374
2374
|
let signalConnected;
|
|
2375
2375
|
const connected = new Promise((resolve) => {
|
|
@@ -2468,7 +2468,7 @@ const statusCommand = {
|
|
|
2468
2468
|
console.log("No machines registered. Run `skydive portal open` to register this one.");
|
|
2469
2469
|
return;
|
|
2470
2470
|
}
|
|
2471
|
-
const { headers, rows } = buildDeviceTable(devices, agents, (await resolveMachineIdentity()).machineName);
|
|
2471
|
+
const { headers, rows } = buildDeviceTable(devices, agents, (await resolveMachineIdentity(null)).machineName);
|
|
2472
2472
|
printTable(headers, rows);
|
|
2473
2473
|
}
|
|
2474
2474
|
};
|
|
@@ -4215,7 +4215,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4215
4215
|
process.exit(0);
|
|
4216
4216
|
}
|
|
4217
4217
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4218
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4218
|
+
const { runPortalDaemon } = await import("./daemon-Djei8xaW.mjs");
|
|
4219
4219
|
runPortalDaemon(process.argv);
|
|
4220
4220
|
} else runCli();
|
|
4221
4221
|
function runCli() {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-
|
|
2
|
+
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-B97UpfHm.mjs";
|
|
3
3
|
import { D as getReviewStateDir, H as saveTheme, N as recordDefaultAgent, O as getSavedTheme, S as getConfigPath, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams, v as DEFAULT_API_URL, y as DEFAULT_APP_URL, z as resolveWebUrl } from "./print-riiY5Gly.mjs";
|
|
4
4
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
5
5
|
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-CLrHVkxh.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
|
|
7
|
-
import { t as PortalClient } from "./client-
|
|
8
|
-
import "./daemon-
|
|
7
|
+
import { t as PortalClient } from "./client-DbqRBquD.mjs";
|
|
8
|
+
import "./daemon-eb9dm6Ih.mjs";
|
|
9
9
|
import "./api-DG5W6iwx.mjs";
|
|
10
10
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
11
|
-
import { t as PortalDaemonClient } from "./daemon-client-
|
|
11
|
+
import { t as PortalDaemonClient } from "./daemon-client-N8rvGusv.mjs";
|
|
12
12
|
import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
|
|
13
13
|
import * as os$1 from "node:os";
|
|
14
14
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -386,6 +386,8 @@ function createPortalClient(opts) {
|
|
|
386
386
|
deviceToken: null
|
|
387
387
|
}),
|
|
388
388
|
resolveCwd: () => process.cwd(),
|
|
389
|
+
persistedMachineName: null,
|
|
390
|
+
onMachineName: () => {},
|
|
389
391
|
onState: (state) => opts.onState({
|
|
390
392
|
status: state.status,
|
|
391
393
|
machineName: state.machineName,
|
|
@@ -1078,42 +1080,133 @@ function createAgentHost({ renderer, notifications }) {
|
|
|
1078
1080
|
* take the one host we can identify unambiguously (github.com, incl. `www.`),
|
|
1079
1081
|
* which covers the common product case; GHES stays the native badge's job.
|
|
1080
1082
|
*
|
|
1081
|
-
* Only the `/pull/<n>` form (not `/issues
|
|
1082
|
-
*
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
*/
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1083
|
+
* Only the `/pull/<n>` form (not `/issues/`). A trailing path/query/fragment
|
|
1084
|
+
* (e.g. `/files`, `#discussion`) is allowed but not captured into the canonical
|
|
1085
|
+
* URL. Also matches inside markdown links, backticks, and trailing punctuation.
|
|
1086
|
+
*
|
|
1087
|
+
* The `https://` (and `www.`) prefix is optional: agents and people routinely
|
|
1088
|
+
* write a bare `github.com/o/r/pull/7`, and that should link a PR the same way
|
|
1089
|
+
* the full URL does. To keep a bare host from matching inside a longer hostname
|
|
1090
|
+
* (`mygithub.com/...`, `notgithub.com/...`), a protocol-less match must be
|
|
1091
|
+
* preceded by a start-of-string or a non-host character (anything but a word
|
|
1092
|
+
* char or `.`) — a leading boundary the `(?:^|[^\w.])` group asserts and
|
|
1093
|
+
* consumes. The consumed boundary char is outside the captured groups, so it
|
|
1094
|
+
* never leaks into owner/repo. `matchAll` won't overlap, but the boundary is a
|
|
1095
|
+
* single delimiter (space, `(`, `[`, etc.) between PRs in practice, so distinct
|
|
1096
|
+
* back-to-back URLs still each match.
|
|
1097
|
+
*/
|
|
1098
|
+
const PR_URL_RE = /(?:^|[^\w.])(?:https?:\/\/)?(?:www\.)?github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/(\d+)/gi;
|
|
1099
|
+
/**
|
|
1100
|
+
* Matches a bare, human-style PR reference `#<n>` — the way people (and LLMs)
|
|
1101
|
+
* name a PR in prose ("opened #20858", "landed #42"). Requires a non-word
|
|
1102
|
+
* boundary before the `#` so a fragment tail like `/pull/7#discussion_r5` or a
|
|
1103
|
+
* hex color `#20858` embedded in a word doesn't count, and captures the digits
|
|
1104
|
+
* up to a word boundary. We only harvest these from **assistant prose**, never
|
|
1105
|
+
* from tool output, because `#N` is exactly the relevance signal we're after:
|
|
1106
|
+
* the agent chose to talk about this PR.
|
|
1107
|
+
*/
|
|
1108
|
+
const PR_HASH_RE = /(?:^|[^\w#/])#(\d+)\b/g;
|
|
1109
|
+
/**
|
|
1110
|
+
* Every full `/pull/<n>` github.com URL in `urlText`, keyed by number to the
|
|
1111
|
+
* candidate refs for that number. A number can map to more than one repo, so we
|
|
1112
|
+
* keep all candidates; a bare `#N` label can't say which repo, but the pill's
|
|
1113
|
+
* own URL does. Deduped by repo-qualified identity (`owner/repo#number`) so the
|
|
1114
|
+
* same PR linked twice — even with a different trailing path/fragment —
|
|
1115
|
+
* collapses to one ref.
|
|
1116
|
+
*
|
|
1117
|
+
* `deferTrailingNumber` guards the streaming truncation case: when true, a URL
|
|
1118
|
+
* whose number runs to the very end of `urlText` is skipped because the next
|
|
1119
|
+
* delta may extend those digits (`.../pull/19` now, `.../pull/19739` later).
|
|
1120
|
+
* Only the still-growing PROSE buffer sets this; settled tool output is always
|
|
1121
|
+
* complete, so its URLs are harvested with the guard off.
|
|
1122
|
+
*/
|
|
1123
|
+
function urlsByNumber(urlText, deferTrailingNumber, byNumber) {
|
|
1124
|
+
for (const match of urlText.matchAll(PR_URL_RE)) {
|
|
1100
1125
|
const owner = match[1];
|
|
1101
1126
|
const repo = match[2];
|
|
1102
1127
|
const numberText = match[3];
|
|
1103
1128
|
if (!owner || !repo || !numberText) continue;
|
|
1104
|
-
if (
|
|
1105
|
-
if (match.index + match[0].length >=
|
|
1129
|
+
if (deferTrailingNumber) {
|
|
1130
|
+
if (match.index + match[0].length >= urlText.length) continue;
|
|
1106
1131
|
}
|
|
1107
1132
|
const number = Number.parseInt(numberText, 10);
|
|
1108
1133
|
if (!Number.isFinite(number) || number <= 0) continue;
|
|
1109
|
-
const
|
|
1110
|
-
if (byKey.has(key)) continue;
|
|
1111
|
-
byKey.set(key, {
|
|
1134
|
+
const ref = {
|
|
1112
1135
|
number,
|
|
1113
|
-
key
|
|
1136
|
+
key: `${owner.toLowerCase()}/${repo.toLowerCase()}#${number}`,
|
|
1114
1137
|
url: `https://github.com/${owner}/${repo}/pull/${number}`
|
|
1115
|
-
}
|
|
1138
|
+
};
|
|
1139
|
+
const existing = byNumber.get(number);
|
|
1140
|
+
if (existing) {
|
|
1141
|
+
if (!existing.some((r) => r.key === ref.key)) existing.push(ref);
|
|
1142
|
+
} else byNumber.set(number, [ref]);
|
|
1116
1143
|
}
|
|
1144
|
+
}
|
|
1145
|
+
/** Every human-style `#N` named in `proseText` — the relevance signal. */
|
|
1146
|
+
function hashNumbers(proseText) {
|
|
1147
|
+
const numbers = /* @__PURE__ */ new Set();
|
|
1148
|
+
for (const match of proseText.matchAll(PR_HASH_RE)) {
|
|
1149
|
+
const numberText = match[1];
|
|
1150
|
+
if (!numberText) continue;
|
|
1151
|
+
const number = Number.parseInt(numberText, 10);
|
|
1152
|
+
if (Number.isFinite(number) && number > 0) numbers.add(number);
|
|
1153
|
+
}
|
|
1154
|
+
return numbers;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* The relevance contract, in one place.
|
|
1158
|
+
*
|
|
1159
|
+
* A GitHub PR URL is cheap to emit and shows up in bulk any time a tool dumps
|
|
1160
|
+
* PRs the conversation isn't actually about — `gh pr list`, `gh pr status`, a
|
|
1161
|
+
* `gh search` — printing dozens of `/pull/N` URLs the agent never discusses.
|
|
1162
|
+
* Pinning a sidebar pill for every one of those is noise.
|
|
1163
|
+
*
|
|
1164
|
+
* So a PR is surfaced only when BOTH hold:
|
|
1165
|
+
* 1. a fully-qualified `github.com/<o>/<r>/pull/<n>` URL exists — this may
|
|
1166
|
+
* come from the assistant's prose OR from tool output (a `gh pr create`
|
|
1167
|
+
* that printed the URL the agent then referenced by number), and
|
|
1168
|
+
* 2. the **assistant's prose** names the number as a human-style `#<n>`.
|
|
1169
|
+
*
|
|
1170
|
+
* The `#N` in prose is the intent signal — "the agent is talking about this
|
|
1171
|
+
* PR", as opposed to "a tool happened to list it". The full URL (from wherever)
|
|
1172
|
+
* proves the PR is real and pins down its repo + canonical link. A URL with no
|
|
1173
|
+
* `#N` in prose (the whole of a `gh pr list` dump) is dropped; a `#N` with no
|
|
1174
|
+
* matching URL anywhere (a bare issue-style ref) is dropped.
|
|
1175
|
+
*
|
|
1176
|
+
* `proseText` is the assistant's prose alone (the only source of the `#N`
|
|
1177
|
+
* relevance signal); `urlOnlyText` is settled tool output that may carry full
|
|
1178
|
+
* PR URLs but no `#N` intent. URLs are harvested from both; `#N` from prose
|
|
1179
|
+
* only.
|
|
1180
|
+
*/
|
|
1181
|
+
function collectRelevant(proseText, urlOnlyText, options, byKey) {
|
|
1182
|
+
const byNumber = /* @__PURE__ */ new Map();
|
|
1183
|
+
urlsByNumber(proseText, options.requireCompleteNumber, byNumber);
|
|
1184
|
+
if (urlOnlyText) urlsByNumber(urlOnlyText, false, byNumber);
|
|
1185
|
+
if (byNumber.size === 0) return;
|
|
1186
|
+
const named = hashNumbers(proseText);
|
|
1187
|
+
if (named.size === 0) return;
|
|
1188
|
+
for (const [number, refs] of byNumber) {
|
|
1189
|
+
if (!named.has(number)) continue;
|
|
1190
|
+
for (const ref of refs) if (!byKey.has(ref.key)) byKey.set(ref.key, ref);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Extracts every distinct GitHub PR that is both linked (a full `/pull/<n>`
|
|
1195
|
+
* URL) AND named (`#<n>`), de-duplicated by **repo-qualified identity**
|
|
1196
|
+
* (`owner/repo#number`) — NOT by number, since `#42` can exist in many repos.
|
|
1197
|
+
*
|
|
1198
|
+
* The two-argument form takes the URL pool and the prose separately, so a URL
|
|
1199
|
+
* from tool output can satisfy the link requirement while the `#N` stays a
|
|
1200
|
+
* prose-only signal. The one-argument form (prose only) is the common case: it
|
|
1201
|
+
* requires the URL to be in the same prose that names it.
|
|
1202
|
+
*
|
|
1203
|
+
* See {@link collectRelevant} for the contract.
|
|
1204
|
+
*/
|
|
1205
|
+
function extractPullRequests(proseText, options) {
|
|
1206
|
+
const requireCompleteNumber = options?.requireCompleteNumber ?? true;
|
|
1207
|
+
const urlOnly = options?.urlOnlyText ?? "";
|
|
1208
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1209
|
+
collectRelevant(proseText, urlOnly, { requireCompleteNumber }, byKey);
|
|
1117
1210
|
return [...byKey.values()];
|
|
1118
1211
|
}
|
|
1119
1212
|
/**
|
|
@@ -1152,28 +1245,34 @@ function stringifyToolOutput(value) {
|
|
|
1152
1245
|
}
|
|
1153
1246
|
/**
|
|
1154
1247
|
* A streaming PR scanner: fed run chunks as they stream, it reports each
|
|
1155
|
-
* distinct PR the moment
|
|
1156
|
-
* the
|
|
1248
|
+
* distinct PR the moment BOTH conditions hold — a full `/pull/<n>` URL has been
|
|
1249
|
+
* seen (in prose OR tool output) AND the assistant's prose names it `#<n>` —
|
|
1250
|
+
* exactly once, without ever rescanning the transcript.
|
|
1157
1251
|
*
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1252
|
+
* Two sources feed it, with different roles:
|
|
1253
|
+
* - **Assistant text** (`text-delta`): the ONLY source of the `#N` relevance
|
|
1254
|
+
* signal, and also a source of URLs. Accumulated per part id (a URL or `#N`
|
|
1255
|
+
* can split across two deltas); only the current part's buffer is scanned.
|
|
1256
|
+
* - **Tool output** (`tool-output-available`): a URL-only source. A
|
|
1257
|
+
* `gh pr create` prints the URL the agent then references by number, so the
|
|
1258
|
+
* URL for a #N mention often lives here, not in the prose. Tool output can
|
|
1259
|
+
* NEVER originate a pill on its own — a `gh pr list` dump (URLs, no `#N`)
|
|
1260
|
+
* stays silent — but it satisfies the URL half for a PR the prose names.
|
|
1165
1261
|
*
|
|
1166
1262
|
* `onPullRequest` fires once per never-before-seen PR (keyed by
|
|
1167
|
-
* `owner/repo#number`)
|
|
1168
|
-
*
|
|
1169
|
-
* same chunks the transcript reducer sees.
|
|
1263
|
+
* `owner/repo#number`). Construct one per run (or per conversation) and feed it
|
|
1264
|
+
* the same chunks the transcript reducer sees. See {@link collectRelevant}.
|
|
1170
1265
|
*/
|
|
1171
1266
|
function createPullRequestScanner(onPullRequest) {
|
|
1172
1267
|
let text = "";
|
|
1173
1268
|
let lastPartId;
|
|
1269
|
+
let toolUrlText = "";
|
|
1174
1270
|
const seen = /* @__PURE__ */ new Set();
|
|
1175
|
-
const report = (
|
|
1176
|
-
for (const pr of extractPullRequests(
|
|
1271
|
+
const report = (final) => {
|
|
1272
|
+
for (const pr of extractPullRequests(text, {
|
|
1273
|
+
requireCompleteNumber: !final,
|
|
1274
|
+
urlOnlyText: toolUrlText
|
|
1275
|
+
})) {
|
|
1177
1276
|
if (seen.has(pr.key)) continue;
|
|
1178
1277
|
seen.add(pr.key);
|
|
1179
1278
|
onPullRequest(pr);
|
|
@@ -1183,7 +1282,8 @@ function createPullRequestScanner(onPullRequest) {
|
|
|
1183
1282
|
if (chunk["type"] === "tool-output-available") {
|
|
1184
1283
|
const outputText = stringifyToolOutput(chunk["output"]);
|
|
1185
1284
|
if (!outputText.includes("/pull/")) return;
|
|
1186
|
-
|
|
1285
|
+
toolUrlText += `\n${outputText}`;
|
|
1286
|
+
report(true);
|
|
1187
1287
|
return;
|
|
1188
1288
|
}
|
|
1189
1289
|
if (chunk["type"] !== "text-delta") return;
|
|
@@ -1191,13 +1291,12 @@ function createPullRequestScanner(onPullRequest) {
|
|
|
1191
1291
|
if (!delta) return;
|
|
1192
1292
|
const partId = typeof chunk["id"] === "string" ? chunk["id"] : void 0;
|
|
1193
1293
|
if (partId && lastPartId && partId !== lastPartId) {
|
|
1194
|
-
|
|
1294
|
+
report(true);
|
|
1195
1295
|
text = "";
|
|
1196
1296
|
}
|
|
1197
1297
|
text += delta;
|
|
1198
1298
|
lastPartId = partId ?? lastPartId;
|
|
1199
|
-
|
|
1200
|
-
report(text, false);
|
|
1299
|
+
report(false);
|
|
1201
1300
|
} };
|
|
1202
1301
|
}
|
|
1203
1302
|
|
|
@@ -10884,10 +10983,13 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10884
10983
|
text: recapText
|
|
10885
10984
|
});
|
|
10886
10985
|
setItems(loaded);
|
|
10986
|
+
const toolUrlText = loaded.map((item) => item.kind === "tool" && item.output?.kind === "ok" ? stringifyToolOutput(item.output.value) : "").join("\n");
|
|
10887
10987
|
for (const item of loaded) {
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10988
|
+
if (item.kind !== "assistant-text") continue;
|
|
10989
|
+
for (const pr of extractPullRequests(item.text, {
|
|
10990
|
+
requireCompleteNumber: false,
|
|
10991
|
+
urlOnlyText: toolUrlText
|
|
10992
|
+
})) {
|
|
10891
10993
|
if (reportedPrsRef.current.has(pr.key)) continue;
|
|
10892
10994
|
reportedPrsRef.current.set(pr.key, pr);
|
|
10893
10995
|
setPrLinks((prev) => [...prev, pr]);
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
import { a as mintPortalDeviceToken, c as unifyLegacyCliGrants, i as grantPortalAccess, n as fetchPortalDevices, r as findThisDevice } from "./api-DG5W6iwx.mjs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import
|
|
5
|
+
import * as childProcess from "node:child_process";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
6
7
|
import net from "node:net";
|
|
7
8
|
import { WebSocket, createWebSocketStream } from "ws";
|
|
8
9
|
|
|
9
10
|
//#region ../portal-daemon/src/machine.ts
|
|
11
|
+
const SCUTIL_TIMEOUT_MS = 2e3;
|
|
12
|
+
const SCUTIL_ATTEMPTS = 3;
|
|
10
13
|
/**
|
|
11
14
|
* Identity this machine registers under on the portal.
|
|
12
15
|
*
|
|
@@ -15,38 +18,74 @@ import { WebSocket, createWebSocketStream } from "ws";
|
|
|
15
18
|
* row, so a grant applies to the machine no matter which surface the user
|
|
16
19
|
* shared from. The name must therefore match what the desktop app registers —
|
|
17
20
|
* on macOS that is `scutil --get LocalHostName` (the Rust client's own
|
|
18
|
-
* derivation, stabler than the network-assigned hostname)
|
|
19
|
-
*
|
|
21
|
+
* derivation, stabler than the network-assigned hostname).
|
|
22
|
+
*
|
|
23
|
+
* The machine name is a durable IDENTITY, not a fresh network reading: every
|
|
24
|
+
* grant hangs off the device row keyed by this name, so if it changes the
|
|
25
|
+
* device flips to a brand-new row with ZERO grants and every agent re-prompts
|
|
26
|
+
* across every open CLI. `os.hostname()` is NOT durable — macOS rewrites it
|
|
27
|
+
* from DHCP/DNS per network — so it must never be allowed to silently replace
|
|
28
|
+
* a name we already registered under. Resolution order:
|
|
29
|
+
*
|
|
30
|
+
* 1. `scutil --get LocalHostName` (retried) — the stable, correct source.
|
|
31
|
+
* 2. `previous` — the name we last successfully registered under, persisted
|
|
32
|
+
* by the daemon. Preferred over the volatile hostname so a transient
|
|
33
|
+
* `scutil` failure keeps our existing identity (and grants) intact.
|
|
34
|
+
* 3. `os.hostname()` — last resort for a machine that has NEVER resolved a
|
|
35
|
+
* name (first run, non-macOS, scutil permanently unavailable).
|
|
20
36
|
*
|
|
21
37
|
* Historically the CLI suffixed `-cli` to keep a device distinct from the
|
|
22
38
|
* desktop's; `unifyLegacyCliGrants` migrates grants off those rows.
|
|
23
39
|
*/
|
|
24
|
-
async function resolveMachineIdentity() {
|
|
25
|
-
const
|
|
26
|
-
if (process.platform !== "darwin")
|
|
27
|
-
machineName
|
|
28
|
-
|
|
40
|
+
async function resolveMachineIdentity(previous) {
|
|
41
|
+
const persisted = previous?.trim() || null;
|
|
42
|
+
if (process.platform !== "darwin") {
|
|
43
|
+
const machineName = persisted ?? fallbackHost();
|
|
44
|
+
return {
|
|
45
|
+
machineName,
|
|
46
|
+
friendlyName: machineName,
|
|
47
|
+
machineNameSource: persisted ? "persisted" : "hostname-fallback"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const local = await scutilRead("LocalHostName");
|
|
51
|
+
if (local) return {
|
|
52
|
+
machineName: local,
|
|
53
|
+
friendlyName: await scutilRead("ComputerName") ?? local,
|
|
54
|
+
machineNameSource: "scutil"
|
|
55
|
+
};
|
|
56
|
+
if (persisted) return {
|
|
57
|
+
machineName: persisted,
|
|
58
|
+
friendlyName: persisted,
|
|
59
|
+
machineNameSource: "persisted"
|
|
29
60
|
};
|
|
30
|
-
const
|
|
61
|
+
const fallback = fallbackHost();
|
|
31
62
|
return {
|
|
32
|
-
machineName:
|
|
33
|
-
friendlyName:
|
|
63
|
+
machineName: fallback,
|
|
64
|
+
friendlyName: fallback,
|
|
65
|
+
machineNameSource: "hostname-fallback"
|
|
34
66
|
};
|
|
35
67
|
}
|
|
36
68
|
function fallbackHost() {
|
|
37
69
|
return (os.hostname() || "machine").trim().replace(/\.local$/i, "") || "machine";
|
|
38
70
|
}
|
|
39
71
|
function scutilRead(key) {
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
return attempt(0);
|
|
73
|
+
function attempt(n) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
childProcess.execFile("/usr/sbin/scutil", ["--get", key], { timeout: SCUTIL_TIMEOUT_MS }, (error, stdout) => {
|
|
76
|
+
if (error) {
|
|
77
|
+
if (n + 1 < SCUTIL_ATTEMPTS) {
|
|
78
|
+
resolve(attempt(n + 1));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
resolve(null);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const value = stdout.trim();
|
|
85
|
+
resolve(value.length > 0 ? value : null);
|
|
86
|
+
});
|
|
48
87
|
});
|
|
49
|
-
}
|
|
88
|
+
}
|
|
50
89
|
}
|
|
51
90
|
const INHERITED_ENV = [
|
|
52
91
|
"HOME",
|
|
@@ -550,9 +589,10 @@ var PortalClient = class {
|
|
|
550
589
|
}
|
|
551
590
|
async connectLoop() {
|
|
552
591
|
if (!this.machineName) {
|
|
553
|
-
const identity = await resolveMachineIdentity();
|
|
592
|
+
const identity = await resolveMachineIdentity(this.opts.persistedMachineName);
|
|
554
593
|
this.machineName = identity.machineName;
|
|
555
594
|
this.friendlyName = identity.friendlyName;
|
|
595
|
+
this.opts.onMachineName(identity.machineName, identity.machineNameSource);
|
|
556
596
|
}
|
|
557
597
|
let backoff = INITIAL_BACKOFF_MS;
|
|
558
598
|
while (this.enabled && !this.disposed) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./client-
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-
|
|
2
|
+
import "./client-DbqRBquD.mjs";
|
|
3
|
+
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-eb9dm6Ih.mjs";
|
|
4
4
|
import "./api-DG5W6iwx.mjs";
|
|
5
5
|
|
|
6
6
|
export { runPortalDaemon };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-
|
|
2
|
+
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-eb9dm6Ih.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as isRecord, t as PortalClient } from "./client-
|
|
2
|
+
import { n as isRecord, t as PortalClient } from "./client-DbqRBquD.mjs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -20,7 +20,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
|
|
|
20
20
|
* the commit time of the built tree is one monotonic clock they share.
|
|
21
21
|
*/
|
|
22
22
|
function portalDaemonBuild() {
|
|
23
|
-
return "
|
|
23
|
+
return "1786605017";
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -365,6 +365,7 @@ var PortalDaemon = class {
|
|
|
365
365
|
conns = /* @__PURE__ */ new Set();
|
|
366
366
|
cwds = /* @__PURE__ */ new Map();
|
|
367
367
|
declined = /* @__PURE__ */ new Set();
|
|
368
|
+
persistedMachineName = null;
|
|
368
369
|
fallbackCwd;
|
|
369
370
|
idleTimer = null;
|
|
370
371
|
sessionToken = null;
|
|
@@ -499,12 +500,28 @@ var PortalDaemon = class {
|
|
|
499
500
|
deviceToken: this.deviceToken
|
|
500
501
|
}),
|
|
501
502
|
resolveCwd: (conversationId) => this.resolveCwd(conversationId),
|
|
503
|
+
persistedMachineName: this.persistedMachineName,
|
|
504
|
+
onMachineName: (name, source) => this.onMachineName(name, source),
|
|
502
505
|
onState: (state) => {
|
|
503
506
|
this.lastState = state;
|
|
504
507
|
this.broadcastState();
|
|
505
508
|
}
|
|
506
509
|
});
|
|
507
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* The identity resolved on connect. Persist the name so a later scutil
|
|
513
|
+
* failure reuses it instead of adopting the volatile hostname, and log the
|
|
514
|
+
* source — a `hostname-fallback` after we'd previously registered under
|
|
515
|
+
* scutil is the tell that a flap just orphaned this machine's grants.
|
|
516
|
+
*/
|
|
517
|
+
onMachineName(name, source) {
|
|
518
|
+
if (source === "hostname-fallback" && this.persistedMachineName) this.logInfo(`portal identity WARNING: fell back to hostname "${name}" but had previously registered as "${this.persistedMachineName}" — grants may be orphaned`);
|
|
519
|
+
else this.logInfo(`portal identity: "${name}" (source=${source})`);
|
|
520
|
+
if (name && name !== this.persistedMachineName) {
|
|
521
|
+
this.persistedMachineName = name;
|
|
522
|
+
this.persistState();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
508
525
|
/** The cwd an exec for `conversationId` runs in. */
|
|
509
526
|
resolveCwd(conversationId) {
|
|
510
527
|
if (conversationId) {
|
|
@@ -538,7 +555,14 @@ var PortalDaemon = class {
|
|
|
538
555
|
/** Append a line to the daemon log file (best-effort, for post-hoc debugging). */
|
|
539
556
|
logError(context, error) {
|
|
540
557
|
const message = error instanceof Error ? error.message : String(error);
|
|
541
|
-
|
|
558
|
+
this.logLine(`${context}: ${message}`);
|
|
559
|
+
}
|
|
560
|
+
/** Append an informational line to the daemon log file (best-effort). */
|
|
561
|
+
logInfo(message) {
|
|
562
|
+
this.logLine(message);
|
|
563
|
+
}
|
|
564
|
+
logLine(message) {
|
|
565
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${message}\n`;
|
|
542
566
|
appendFile(this.paths.logPath, line).catch((_error) => {});
|
|
543
567
|
}
|
|
544
568
|
/** Close the listener without exiting the process (tests own the process). */
|
|
@@ -598,6 +622,7 @@ var PortalDaemon = class {
|
|
|
598
622
|
if (Array.isArray(parsed.declined)) {
|
|
599
623
|
for (const id of parsed.declined) if (typeof id === "string") this.declined.add(id);
|
|
600
624
|
}
|
|
625
|
+
if (typeof parsed.machineName === "string" && parsed.machineName) this.persistedMachineName = parsed.machineName;
|
|
601
626
|
}
|
|
602
627
|
} catch (_error) {}
|
|
603
628
|
}
|
|
@@ -605,7 +630,8 @@ var PortalDaemon = class {
|
|
|
605
630
|
const state = {
|
|
606
631
|
version: LOCAL_PROTOCOL_VERSION,
|
|
607
632
|
cwds: Object.fromEntries(this.cwds),
|
|
608
|
-
declined: [...this.declined]
|
|
633
|
+
declined: [...this.declined],
|
|
634
|
+
machineName: this.persistedMachineName ?? void 0
|
|
609
635
|
};
|
|
610
636
|
writeFile(this.paths.statePath, JSON.stringify(state)).catch((error) => {
|
|
611
637
|
this.logError("persistState failed", error);
|
|
@@ -14,7 +14,7 @@ import { n as printError } from "./output-DYzzdXYV.mjs";
|
|
|
14
14
|
* the reply (and to --json).
|
|
15
15
|
*/
|
|
16
16
|
async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
17
|
-
const { PortalClient } = await import("./client-
|
|
17
|
+
const { PortalClient } = await import("./client-hH2PJL8y.mjs");
|
|
18
18
|
let signalConnected;
|
|
19
19
|
const connected = new Promise((resolve) => {
|
|
20
20
|
signalConnected = resolve;
|
|
@@ -26,6 +26,8 @@ async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
|
26
26
|
deviceToken: null
|
|
27
27
|
}),
|
|
28
28
|
resolveCwd: () => process.cwd(),
|
|
29
|
+
persistedMachineName: null,
|
|
30
|
+
onMachineName: () => {},
|
|
29
31
|
onState: (state) => {
|
|
30
32
|
if (state.status === "connected") signalConnected();
|
|
31
33
|
if (state.status === "error") console.error(`portal: connection error: ${state.error ?? "unknown"}; retrying`);
|
package/package.json
CHANGED