oysterun 1.5.2 → 1.5.3-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1145,6 +1145,48 @@
|
|
|
1145
1145
|
justify-content: center;
|
|
1146
1146
|
}
|
|
1147
1147
|
|
|
1148
|
+
.agent-store-demo-backdrop {
|
|
1149
|
+
z-index: 10020;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.agent-store-demo-status {
|
|
1153
|
+
min-height: 42px;
|
|
1154
|
+
margin-top: 14px;
|
|
1155
|
+
display: flex;
|
|
1156
|
+
align-items: center;
|
|
1157
|
+
gap: 10px;
|
|
1158
|
+
color: var(--ink-soft);
|
|
1159
|
+
font-size: 14px;
|
|
1160
|
+
line-height: 1.45;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
.agent-store-demo-spinner {
|
|
1164
|
+
width: 16px;
|
|
1165
|
+
height: 16px;
|
|
1166
|
+
flex: 0 0 16px;
|
|
1167
|
+
border: 2px solid rgba(151, 89, 49, 0.22);
|
|
1168
|
+
border-top-color: var(--accent);
|
|
1169
|
+
border-radius: 50%;
|
|
1170
|
+
animation: oysterun-auth-spin 800ms linear infinite;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.browse-create-folder-modal .agent-store-demo-actions {
|
|
1174
|
+
display: grid;
|
|
1175
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1176
|
+
gap: 10px;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.agent-store-demo-actions > button {
|
|
1180
|
+
min-width: 0;
|
|
1181
|
+
justify-content: center;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
#agent-store-demo-submit-btn {
|
|
1185
|
+
display: inline-flex;
|
|
1186
|
+
align-items: center;
|
|
1187
|
+
gap: 9px;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1148
1190
|
#app {
|
|
1149
1191
|
display: none;
|
|
1150
1192
|
height: 100vh;
|
|
@@ -7846,6 +7888,26 @@
|
|
|
7846
7888
|
>
|
|
7847
7889
|
</div>
|
|
7848
7890
|
<div class="topbar-right">
|
|
7891
|
+
<button
|
|
7892
|
+
class="sessions-compose-button topbar-agent-store hidden"
|
|
7893
|
+
id="entry-agent-store-btn"
|
|
7894
|
+
type="button"
|
|
7895
|
+
aria-label="Agent Store"
|
|
7896
|
+
title="Agent Store"
|
|
7897
|
+
data-testid="oysterun-agent-store-demo"
|
|
7898
|
+
onClick=${call("openAgentStoreDemo")}
|
|
7899
|
+
>
|
|
7900
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
7901
|
+
<path
|
|
7902
|
+
d="M4 9.25h16v10.5H4V9.25Zm2-5h12l2 5H4l2-5Zm4 5v10.5m4-10.5v10.5"
|
|
7903
|
+
fill="none"
|
|
7904
|
+
stroke="currentColor"
|
|
7905
|
+
stroke-width="1.7"
|
|
7906
|
+
stroke-linecap="round"
|
|
7907
|
+
stroke-linejoin="round"
|
|
7908
|
+
></path>
|
|
7909
|
+
</svg>
|
|
7910
|
+
</button>
|
|
7849
7911
|
<button
|
|
7850
7912
|
class="sessions-compose-button topbar-sessions-compose hidden"
|
|
7851
7913
|
id="entry-start-session-btn"
|
|
@@ -11040,6 +11102,126 @@ Terminal output will appear here.</pre
|
|
|
11040
11102
|
`;
|
|
11041
11103
|
}
|
|
11042
11104
|
|
|
11105
|
+
function AgentStoreDemoModal() {
|
|
11106
|
+
return html`
|
|
11107
|
+
<div
|
|
11108
|
+
id="agent-store-demo-backdrop"
|
|
11109
|
+
class="modal-backdrop sheet browse-create-folder-backdrop agent-store-demo-backdrop hidden"
|
|
11110
|
+
onClick=${closeBackdrop("closeAgentStoreDemo")}
|
|
11111
|
+
>
|
|
11112
|
+
<div
|
|
11113
|
+
class="modal sheet narrow browse-create-folder-modal agent-store-demo-modal"
|
|
11114
|
+
role="dialog"
|
|
11115
|
+
aria-modal="true"
|
|
11116
|
+
aria-labelledby="agent-store-demo-title"
|
|
11117
|
+
data-oysterun-agent-store-demo="true"
|
|
11118
|
+
>
|
|
11119
|
+
<button
|
|
11120
|
+
class="modal-close-btn browse-create-folder-close-btn"
|
|
11121
|
+
id="agent-store-demo-close-btn"
|
|
11122
|
+
type="button"
|
|
11123
|
+
aria-label="Close"
|
|
11124
|
+
title="Close"
|
|
11125
|
+
onClick=${call("closeAgentStoreDemo")}
|
|
11126
|
+
>
|
|
11127
|
+
x
|
|
11128
|
+
</button>
|
|
11129
|
+
<div class="modal-scroll">
|
|
11130
|
+
<span class="eyebrow">Agent Store Demo</span>
|
|
11131
|
+
<h2 id="agent-store-demo-title">Clone GitHub Agent</h2>
|
|
11132
|
+
<div class="field" style="margin-top:14px">
|
|
11133
|
+
<label for="agent-store-demo-repository-select"
|
|
11134
|
+
>Demo Repository</label
|
|
11135
|
+
>
|
|
11136
|
+
<select
|
|
11137
|
+
id="agent-store-demo-repository-select"
|
|
11138
|
+
onChange=${(event) =>
|
|
11139
|
+
window.handleAgentStoreDemoRepositorySelect?.(event)}
|
|
11140
|
+
>
|
|
11141
|
+
<option value="">Choose a demo repository</option>
|
|
11142
|
+
<option
|
|
11143
|
+
value="https://github.com/GoatWang/oysterun-ccstock-workenv"
|
|
11144
|
+
>
|
|
11145
|
+
CCStock WorkEnv
|
|
11146
|
+
</option>
|
|
11147
|
+
<option
|
|
11148
|
+
value="https://github.com/GoatWang/oysterun-ccstock-workenv-en"
|
|
11149
|
+
>
|
|
11150
|
+
CCStock WorkEnv (English)
|
|
11151
|
+
</option>
|
|
11152
|
+
<option
|
|
11153
|
+
value="https://github.com/GoatWang/oysterun-github-tracker"
|
|
11154
|
+
>
|
|
11155
|
+
GitHub Tracker
|
|
11156
|
+
</option>
|
|
11157
|
+
</select>
|
|
11158
|
+
</div>
|
|
11159
|
+
<div class="field" style="margin-top:14px">
|
|
11160
|
+
<label for="agent-store-demo-url-input"
|
|
11161
|
+
>GitHub Repository URL</label
|
|
11162
|
+
>
|
|
11163
|
+
<input
|
|
11164
|
+
id="agent-store-demo-url-input"
|
|
11165
|
+
type="url"
|
|
11166
|
+
inputmode="url"
|
|
11167
|
+
autocomplete="url"
|
|
11168
|
+
autocapitalize="none"
|
|
11169
|
+
spellcheck="false"
|
|
11170
|
+
placeholder="https://github.com/owner/repository"
|
|
11171
|
+
onInput=${(event) =>
|
|
11172
|
+
window.handleAgentStoreDemoUrlInput?.(event)}
|
|
11173
|
+
onKeyDown=${(event) =>
|
|
11174
|
+
window.handleAgentStoreDemoUrlKeyDown?.(event)}
|
|
11175
|
+
/>
|
|
11176
|
+
</div>
|
|
11177
|
+
<div
|
|
11178
|
+
id="agent-store-demo-status"
|
|
11179
|
+
class="agent-store-demo-status hidden"
|
|
11180
|
+
role="status"
|
|
11181
|
+
aria-live="polite"
|
|
11182
|
+
aria-atomic="true"
|
|
11183
|
+
>
|
|
11184
|
+
<span
|
|
11185
|
+
id="agent-store-demo-status-spinner"
|
|
11186
|
+
class="agent-store-demo-spinner"
|
|
11187
|
+
aria-hidden="true"
|
|
11188
|
+
></span>
|
|
11189
|
+
<span id="agent-store-demo-status-text"></span>
|
|
11190
|
+
</div>
|
|
11191
|
+
<div
|
|
11192
|
+
id="agent-store-demo-error"
|
|
11193
|
+
class="error-box hidden"
|
|
11194
|
+
style="margin-top:12px"
|
|
11195
|
+
></div>
|
|
11196
|
+
</div>
|
|
11197
|
+
<div class="modal-actions agent-store-demo-actions">
|
|
11198
|
+
<button
|
|
11199
|
+
class="btn-primary"
|
|
11200
|
+
id="agent-store-demo-submit-btn"
|
|
11201
|
+
type="button"
|
|
11202
|
+
onClick=${call("submitAgentStoreDemo")}
|
|
11203
|
+
>
|
|
11204
|
+
<span
|
|
11205
|
+
id="agent-store-demo-submit-spinner"
|
|
11206
|
+
class="agent-store-demo-spinner hidden"
|
|
11207
|
+
aria-hidden="true"
|
|
11208
|
+
></span>
|
|
11209
|
+
<span id="agent-store-demo-submit-label">OK</span>
|
|
11210
|
+
</button>
|
|
11211
|
+
<button
|
|
11212
|
+
class="btn-secondary hidden"
|
|
11213
|
+
id="agent-store-demo-session-setup-btn"
|
|
11214
|
+
type="button"
|
|
11215
|
+
onClick=${call("openAgentStoreDemoSessionSetup")}
|
|
11216
|
+
>
|
|
11217
|
+
Open Session Setup
|
|
11218
|
+
</button>
|
|
11219
|
+
</div>
|
|
11220
|
+
</div>
|
|
11221
|
+
</div>
|
|
11222
|
+
`;
|
|
11223
|
+
}
|
|
11224
|
+
|
|
11043
11225
|
function ProviderAuthModal() {
|
|
11044
11226
|
return html`
|
|
11045
11227
|
<div
|
|
@@ -11380,6 +11562,7 @@ Waiting for Claude login output…</pre
|
|
|
11380
11562
|
<div id="browse-context-menu" class="browse-context-menu hidden" role="menu" aria-hidden="true" tabindex="-1"></div>
|
|
11381
11563
|
<div id="app-toast" class="app-toast hidden" role="status" aria-live="polite" aria-atomic="true"></div>
|
|
11382
11564
|
<${BrowseCreateFolderModal} />
|
|
11565
|
+
<${AgentStoreDemoModal} />
|
|
11383
11566
|
<${ProviderAuthModal} />
|
|
11384
11567
|
<${ChatImagePreviewModal} />
|
|
11385
11568
|
<${HiddenCache} />
|
|
@@ -13211,6 +13394,18 @@ Waiting for Claude login output…</pre
|
|
|
13211
13394
|
error: "",
|
|
13212
13395
|
};
|
|
13213
13396
|
}
|
|
13397
|
+
|
|
13398
|
+
function createAgentStoreDemoState() {
|
|
13399
|
+
return {
|
|
13400
|
+
visible: false,
|
|
13401
|
+
phase: "idle",
|
|
13402
|
+
repositoryUrl: "",
|
|
13403
|
+
status: "",
|
|
13404
|
+
error: "",
|
|
13405
|
+
clone: null,
|
|
13406
|
+
requestId: 0,
|
|
13407
|
+
};
|
|
13408
|
+
}
|
|
13214
13409
|
function createTerminalViewState() {
|
|
13215
13410
|
return {
|
|
13216
13411
|
terminal: null,
|
|
@@ -13386,6 +13581,7 @@ Waiting for Claude login output…</pre
|
|
|
13386
13581
|
updatedAt: 0,
|
|
13387
13582
|
};
|
|
13388
13583
|
let providerAuthModalState = createProviderAuthModalState();
|
|
13584
|
+
let agentStoreDemoState = createAgentStoreDemoState();
|
|
13389
13585
|
let chatImagePreviewState = {
|
|
13390
13586
|
visible: false,
|
|
13391
13587
|
requestKey: "",
|
|
@@ -18218,6 +18414,376 @@ Waiting for Claude login output…</pre
|
|
|
18218
18414
|
window.openExplorerFromSessionsEmptyState =
|
|
18219
18415
|
openExplorerFromSessionsEmptyState;
|
|
18220
18416
|
|
|
18417
|
+
const AGENT_STORE_DEMO_PENDING_PHASES = new Set([
|
|
18418
|
+
"validating",
|
|
18419
|
+
"cloning",
|
|
18420
|
+
"loading_config",
|
|
18421
|
+
"starting_session",
|
|
18422
|
+
]);
|
|
18423
|
+
|
|
18424
|
+
const AGENT_STORE_DEMO_REPOSITORY_URLS = new Set([
|
|
18425
|
+
"https://github.com/GoatWang/oysterun-ccstock-workenv",
|
|
18426
|
+
"https://github.com/GoatWang/oysterun-ccstock-workenv-en",
|
|
18427
|
+
"https://github.com/GoatWang/oysterun-github-tracker",
|
|
18428
|
+
]);
|
|
18429
|
+
|
|
18430
|
+
function isAgentStoreDemoPending() {
|
|
18431
|
+
return AGENT_STORE_DEMO_PENDING_PHASES.has(agentStoreDemoState.phase);
|
|
18432
|
+
}
|
|
18433
|
+
|
|
18434
|
+
function getAgentStoreDemoPendingLabel() {
|
|
18435
|
+
if (agentStoreDemoState.phase === "validating") return "Validating...";
|
|
18436
|
+
if (agentStoreDemoState.phase === "cloning") return "Cloning...";
|
|
18437
|
+
if (agentStoreDemoState.phase === "loading_config")
|
|
18438
|
+
return "Loading config...";
|
|
18439
|
+
if (agentStoreDemoState.phase === "starting_session")
|
|
18440
|
+
return "Starting session...";
|
|
18441
|
+
return "OK";
|
|
18442
|
+
}
|
|
18443
|
+
|
|
18444
|
+
function renderAgentStoreDemo() {
|
|
18445
|
+
const backdrop = $("agent-store-demo-backdrop");
|
|
18446
|
+
if (!backdrop) return;
|
|
18447
|
+
const pending = isAgentStoreDemoPending();
|
|
18448
|
+
const cloneComplete = Boolean(agentStoreDemoState.clone?.agent_folder);
|
|
18449
|
+
const input = $("agent-store-demo-url-input");
|
|
18450
|
+
const repositorySelect = $("agent-store-demo-repository-select");
|
|
18451
|
+
const closeButton = $("agent-store-demo-close-btn");
|
|
18452
|
+
const submitButton = $("agent-store-demo-submit-btn");
|
|
18453
|
+
const submitLabel = $("agent-store-demo-submit-label");
|
|
18454
|
+
|
|
18455
|
+
backdrop.classList.toggle(
|
|
18456
|
+
"hidden",
|
|
18457
|
+
agentStoreDemoState.visible !== true
|
|
18458
|
+
);
|
|
18459
|
+
backdrop.dataset.phase = agentStoreDemoState.phase;
|
|
18460
|
+
if (input) {
|
|
18461
|
+
if (input.value !== agentStoreDemoState.repositoryUrl) {
|
|
18462
|
+
input.value = agentStoreDemoState.repositoryUrl;
|
|
18463
|
+
}
|
|
18464
|
+
input.disabled = pending || cloneComplete;
|
|
18465
|
+
}
|
|
18466
|
+
if (repositorySelect) {
|
|
18467
|
+
repositorySelect.value = AGENT_STORE_DEMO_REPOSITORY_URLS.has(
|
|
18468
|
+
agentStoreDemoState.repositoryUrl
|
|
18469
|
+
)
|
|
18470
|
+
? agentStoreDemoState.repositoryUrl
|
|
18471
|
+
: "";
|
|
18472
|
+
repositorySelect.disabled = pending || cloneComplete;
|
|
18473
|
+
}
|
|
18474
|
+
if (closeButton) closeButton.disabled = pending;
|
|
18475
|
+
if (submitButton) {
|
|
18476
|
+
submitButton.disabled =
|
|
18477
|
+
pending ||
|
|
18478
|
+
cloneComplete ||
|
|
18479
|
+
!agentStoreDemoState.repositoryUrl.trim();
|
|
18480
|
+
submitButton.classList.toggle(
|
|
18481
|
+
"hidden",
|
|
18482
|
+
agentStoreDemoState.phase === "failed_after_clone"
|
|
18483
|
+
);
|
|
18484
|
+
}
|
|
18485
|
+
if (submitLabel)
|
|
18486
|
+
submitLabel.textContent = getAgentStoreDemoPendingLabel();
|
|
18487
|
+
setVisible("agent-store-demo-submit-spinner", pending);
|
|
18488
|
+
setVisible("agent-store-demo-status", pending);
|
|
18489
|
+
setText(
|
|
18490
|
+
"agent-store-demo-status-text",
|
|
18491
|
+
agentStoreDemoState.status || ""
|
|
18492
|
+
);
|
|
18493
|
+
showError("agent-store-demo-error", agentStoreDemoState.error || "");
|
|
18494
|
+
setVisible(
|
|
18495
|
+
"agent-store-demo-session-setup-btn",
|
|
18496
|
+
agentStoreDemoState.phase === "failed_after_clone" && cloneComplete
|
|
18497
|
+
);
|
|
18498
|
+
}
|
|
18499
|
+
|
|
18500
|
+
function openAgentStoreDemo() {
|
|
18501
|
+
if (isAgentStoreDemoPending()) return;
|
|
18502
|
+
agentStoreDemoState = {
|
|
18503
|
+
...createAgentStoreDemoState(),
|
|
18504
|
+
visible: true,
|
|
18505
|
+
requestId: agentStoreDemoState.requestId + 1,
|
|
18506
|
+
};
|
|
18507
|
+
renderAgentStoreDemo();
|
|
18508
|
+
requestAnimationFrame(() => {
|
|
18509
|
+
$("agent-store-demo-url-input")?.focus({ preventScroll: true });
|
|
18510
|
+
});
|
|
18511
|
+
}
|
|
18512
|
+
|
|
18513
|
+
function closeAgentStoreDemo(opts = {}) {
|
|
18514
|
+
if (isAgentStoreDemoPending() && opts.force !== true) return;
|
|
18515
|
+
agentStoreDemoState = {
|
|
18516
|
+
...createAgentStoreDemoState(),
|
|
18517
|
+
requestId: agentStoreDemoState.requestId + 1,
|
|
18518
|
+
};
|
|
18519
|
+
renderAgentStoreDemo();
|
|
18520
|
+
}
|
|
18521
|
+
|
|
18522
|
+
function handleAgentStoreDemoUrlInput(event) {
|
|
18523
|
+
if (isAgentStoreDemoPending() || agentStoreDemoState.clone) return;
|
|
18524
|
+
agentStoreDemoState = {
|
|
18525
|
+
...agentStoreDemoState,
|
|
18526
|
+
phase: "idle",
|
|
18527
|
+
repositoryUrl: event?.target?.value || "",
|
|
18528
|
+
status: "",
|
|
18529
|
+
error: "",
|
|
18530
|
+
};
|
|
18531
|
+
renderAgentStoreDemo();
|
|
18532
|
+
}
|
|
18533
|
+
|
|
18534
|
+
function handleAgentStoreDemoRepositorySelect(event) {
|
|
18535
|
+
if (isAgentStoreDemoPending() || agentStoreDemoState.clone) return;
|
|
18536
|
+
const repositoryUrl = String(event?.target?.value || "").trim();
|
|
18537
|
+
if (!repositoryUrl) return;
|
|
18538
|
+
agentStoreDemoState = {
|
|
18539
|
+
...agentStoreDemoState,
|
|
18540
|
+
phase: "idle",
|
|
18541
|
+
repositoryUrl,
|
|
18542
|
+
status: "",
|
|
18543
|
+
error: "",
|
|
18544
|
+
};
|
|
18545
|
+
renderAgentStoreDemo();
|
|
18546
|
+
requestAnimationFrame(() => {
|
|
18547
|
+
$("agent-store-demo-url-input")?.focus({ preventScroll: true });
|
|
18548
|
+
});
|
|
18549
|
+
}
|
|
18550
|
+
|
|
18551
|
+
function handleAgentStoreDemoUrlKeyDown(event) {
|
|
18552
|
+
if (event?.key !== "Enter" || event.isComposing) return;
|
|
18553
|
+
event.preventDefault();
|
|
18554
|
+
if (!isAgentStoreDemoPending()) void submitAgentStoreDemo();
|
|
18555
|
+
}
|
|
18556
|
+
|
|
18557
|
+
function validateAgentStoreDemoUrl(value) {
|
|
18558
|
+
let parsed;
|
|
18559
|
+
try {
|
|
18560
|
+
parsed = new URL(String(value || "").trim());
|
|
18561
|
+
} catch {
|
|
18562
|
+
throw new Error("Enter a valid public GitHub HTTPS repository URL.");
|
|
18563
|
+
}
|
|
18564
|
+
if (
|
|
18565
|
+
parsed.protocol !== "https:" ||
|
|
18566
|
+
parsed.hostname.toLowerCase() !== "github.com" ||
|
|
18567
|
+
parsed.username ||
|
|
18568
|
+
parsed.password ||
|
|
18569
|
+
parsed.port ||
|
|
18570
|
+
parsed.search ||
|
|
18571
|
+
parsed.hash ||
|
|
18572
|
+
!/^\/[^/]+\/[^/]+\/?$/.test(parsed.pathname)
|
|
18573
|
+
) {
|
|
18574
|
+
throw new Error(
|
|
18575
|
+
"Only public https://github.com/<owner>/<repo> URLs are supported."
|
|
18576
|
+
);
|
|
18577
|
+
}
|
|
18578
|
+
return parsed.toString();
|
|
18579
|
+
}
|
|
18580
|
+
|
|
18581
|
+
async function getAgentStoreDemoProviderSkillInstallDecision({
|
|
18582
|
+
agentId,
|
|
18583
|
+
agentFolder,
|
|
18584
|
+
provider,
|
|
18585
|
+
}) {
|
|
18586
|
+
const params = new URLSearchParams({
|
|
18587
|
+
agent_id: agentId,
|
|
18588
|
+
agent_folder: agentFolder,
|
|
18589
|
+
provider,
|
|
18590
|
+
});
|
|
18591
|
+
try {
|
|
18592
|
+
const status = await apiWithAuthRetry(
|
|
18593
|
+
"GET",
|
|
18594
|
+
`/agent/provider-skill-status?${params.toString()}`
|
|
18595
|
+
);
|
|
18596
|
+
return (
|
|
18597
|
+
status?.default_checked === true &&
|
|
18598
|
+
status?.can_install === true &&
|
|
18599
|
+
status?.disabled !== true
|
|
18600
|
+
);
|
|
18601
|
+
} catch {
|
|
18602
|
+
return false;
|
|
18603
|
+
}
|
|
18604
|
+
}
|
|
18605
|
+
|
|
18606
|
+
function buildAgentStoreDemoSessionStartPayload({
|
|
18607
|
+
clone,
|
|
18608
|
+
folderConfig,
|
|
18609
|
+
installOysterunSkills,
|
|
18610
|
+
}) {
|
|
18611
|
+
const provider = String(folderConfig?.provider || "").trim();
|
|
18612
|
+
const model = String(folderConfig?.model || "").trim();
|
|
18613
|
+
if (!provider || !model) {
|
|
18614
|
+
throw new Error(
|
|
18615
|
+
"The cloned folder did not resolve a provider and model."
|
|
18616
|
+
);
|
|
18617
|
+
}
|
|
18618
|
+
const payload = {
|
|
18619
|
+
agent_id: clone.agent_id,
|
|
18620
|
+
agent_folder: clone.agent_folder,
|
|
18621
|
+
provider,
|
|
18622
|
+
model,
|
|
18623
|
+
interface_type: normalizeFirstPartyInterfaceType(
|
|
18624
|
+
folderConfig.interfaceType || "coding"
|
|
18625
|
+
),
|
|
18626
|
+
reasoning_effort: folderConfig.reasoningEffort || undefined,
|
|
18627
|
+
dangerous_mode: folderConfig.dangerousMode === true,
|
|
18628
|
+
allow_dangerously_skip_permissions:
|
|
18629
|
+
folderConfig.allowDangerouslySkipPermissions === true,
|
|
18630
|
+
install_oysterun_skills: installOysterunSkills === true,
|
|
18631
|
+
};
|
|
18632
|
+
if (provider === "claude") {
|
|
18633
|
+
payload.permission_mode = folderConfig.permissionMode;
|
|
18634
|
+
} else if (provider === "codex") {
|
|
18635
|
+
payload.approval_policy = folderConfig.approvalPolicy;
|
|
18636
|
+
}
|
|
18637
|
+
const proof = {
|
|
18638
|
+
...buildCreateSessionSetupProviderModelPermissionProof(payload),
|
|
18639
|
+
proof_surface: "visible_agent_store_clone_modal_state",
|
|
18640
|
+
};
|
|
18641
|
+
payload.session_setup_provider_model_permission_proof = proof;
|
|
18642
|
+
return { payload, proof };
|
|
18643
|
+
}
|
|
18644
|
+
|
|
18645
|
+
function finalizeAgentStoreDemoStartedSession(data, expectedProof) {
|
|
18646
|
+
const proofValidation =
|
|
18647
|
+
validateSessionSetupProviderModelPermissionProofResponse(
|
|
18648
|
+
data,
|
|
18649
|
+
expectedProof,
|
|
18650
|
+
{ requireCountableStartResponseProof: true }
|
|
18651
|
+
);
|
|
18652
|
+
if (!proofValidation.ok) {
|
|
18653
|
+
throw new Error(
|
|
18654
|
+
`Session Setup proof validation failed: ${proofValidation.errors.join(
|
|
18655
|
+
" "
|
|
18656
|
+
)}`
|
|
18657
|
+
);
|
|
18658
|
+
}
|
|
18659
|
+
const normalizedData = {
|
|
18660
|
+
...data,
|
|
18661
|
+
session_setup_provider_model_permission_proof: proofValidation.proof,
|
|
18662
|
+
};
|
|
18663
|
+
clearPendingAttachments();
|
|
18664
|
+
primeSelectedAgentFromStart(normalizedData);
|
|
18665
|
+
const startedLiveSession =
|
|
18666
|
+
upsertStartedSessionIntoLiveCatalog(normalizedData);
|
|
18667
|
+
invalidateDashboardDataCaches({
|
|
18668
|
+
catalog: true,
|
|
18669
|
+
sessions: true,
|
|
18670
|
+
history: true,
|
|
18671
|
+
transcripts: true,
|
|
18672
|
+
});
|
|
18673
|
+
if (
|
|
18674
|
+
!startedLiveSession ||
|
|
18675
|
+
startedLiveSession.sessionId !== normalizedData.session_id
|
|
18676
|
+
) {
|
|
18677
|
+
throw new Error(
|
|
18678
|
+
"Agent Store could not materialize the started Host session before opening chat."
|
|
18679
|
+
);
|
|
18680
|
+
}
|
|
18681
|
+
return normalizedData.session_id;
|
|
18682
|
+
}
|
|
18683
|
+
|
|
18684
|
+
async function submitAgentStoreDemo() {
|
|
18685
|
+
if (isAgentStoreDemoPending() || agentStoreDemoState.clone) return;
|
|
18686
|
+
const requestId = agentStoreDemoState.requestId + 1;
|
|
18687
|
+
agentStoreDemoState = {
|
|
18688
|
+
...agentStoreDemoState,
|
|
18689
|
+
phase: "validating",
|
|
18690
|
+
status: "Validating GitHub repository...",
|
|
18691
|
+
error: "",
|
|
18692
|
+
requestId,
|
|
18693
|
+
};
|
|
18694
|
+
renderAgentStoreDemo();
|
|
18695
|
+
|
|
18696
|
+
try {
|
|
18697
|
+
const repositoryUrl = validateAgentStoreDemoUrl(
|
|
18698
|
+
agentStoreDemoState.repositoryUrl
|
|
18699
|
+
);
|
|
18700
|
+
agentStoreDemoState = {
|
|
18701
|
+
...agentStoreDemoState,
|
|
18702
|
+
phase: "cloning",
|
|
18703
|
+
status: "Cloning the repository into Default Browse Root...",
|
|
18704
|
+
};
|
|
18705
|
+
renderAgentStoreDemo();
|
|
18706
|
+
const clone = await apiWithAuthRetry(
|
|
18707
|
+
"POST",
|
|
18708
|
+
"/agent-store/demo/clone",
|
|
18709
|
+
{ repository_url: repositoryUrl }
|
|
18710
|
+
);
|
|
18711
|
+
if (agentStoreDemoState.requestId !== requestId) return;
|
|
18712
|
+
|
|
18713
|
+
agentStoreDemoState = {
|
|
18714
|
+
...agentStoreDemoState,
|
|
18715
|
+
phase: "loading_config",
|
|
18716
|
+
status: "Loading the cloned agent configuration...",
|
|
18717
|
+
clone,
|
|
18718
|
+
};
|
|
18719
|
+
renderAgentStoreDemo();
|
|
18720
|
+
const folderConfig = await apiWithAuthRetry(
|
|
18721
|
+
"GET",
|
|
18722
|
+
`/dev/folder-config?path=${encodeURIComponent(clone.agent_folder)}`
|
|
18723
|
+
);
|
|
18724
|
+
if (agentStoreDemoState.requestId !== requestId) return;
|
|
18725
|
+
const installOysterunSkills =
|
|
18726
|
+
await getAgentStoreDemoProviderSkillInstallDecision({
|
|
18727
|
+
agentId: clone.agent_id,
|
|
18728
|
+
agentFolder: clone.agent_folder,
|
|
18729
|
+
provider: folderConfig.provider,
|
|
18730
|
+
});
|
|
18731
|
+
const { payload, proof } = buildAgentStoreDemoSessionStartPayload({
|
|
18732
|
+
clone,
|
|
18733
|
+
folderConfig,
|
|
18734
|
+
installOysterunSkills,
|
|
18735
|
+
});
|
|
18736
|
+
|
|
18737
|
+
agentStoreDemoState = {
|
|
18738
|
+
...agentStoreDemoState,
|
|
18739
|
+
phase: "starting_session",
|
|
18740
|
+
status: "Starting a session with the resolved agent settings...",
|
|
18741
|
+
};
|
|
18742
|
+
renderAgentStoreDemo();
|
|
18743
|
+
const data = await apiWithAuthRetry(
|
|
18744
|
+
"POST",
|
|
18745
|
+
"/session/start",
|
|
18746
|
+
payload
|
|
18747
|
+
);
|
|
18748
|
+
if (agentStoreDemoState.requestId !== requestId) return;
|
|
18749
|
+
const sessionId = finalizeAgentStoreDemoStartedSession(data, proof);
|
|
18750
|
+
closeAgentStoreDemo({ force: true });
|
|
18751
|
+
openRouteCForStartedSession(sessionId);
|
|
18752
|
+
} catch (err) {
|
|
18753
|
+
if (agentStoreDemoState.requestId !== requestId) return;
|
|
18754
|
+
const cloneComplete = Boolean(
|
|
18755
|
+
agentStoreDemoState.clone?.agent_folder
|
|
18756
|
+
);
|
|
18757
|
+
const detail = String(err?.data?.detail || "").trim();
|
|
18758
|
+
const message = cloneComplete
|
|
18759
|
+
? formatCreateStepError("Could not start session", err)
|
|
18760
|
+
: err?.message || "Could not clone the GitHub repository.";
|
|
18761
|
+
agentStoreDemoState = {
|
|
18762
|
+
...agentStoreDemoState,
|
|
18763
|
+
phase: cloneComplete ? "failed_after_clone" : "failed_before_clone",
|
|
18764
|
+
status: "",
|
|
18765
|
+
error: detail ? `${message}\n${detail}` : message,
|
|
18766
|
+
};
|
|
18767
|
+
renderAgentStoreDemo();
|
|
18768
|
+
}
|
|
18769
|
+
}
|
|
18770
|
+
|
|
18771
|
+
function openAgentStoreDemoSessionSetup() {
|
|
18772
|
+
const folder = agentStoreDemoState.clone?.agent_folder || "";
|
|
18773
|
+
if (!folder || isAgentStoreDemoPending()) return;
|
|
18774
|
+
closeAgentStoreDemo({ force: true });
|
|
18775
|
+
openCreateModal(folder);
|
|
18776
|
+
}
|
|
18777
|
+
|
|
18778
|
+
window.openAgentStoreDemo = openAgentStoreDemo;
|
|
18779
|
+
window.closeAgentStoreDemo = closeAgentStoreDemo;
|
|
18780
|
+
window.handleAgentStoreDemoUrlInput = handleAgentStoreDemoUrlInput;
|
|
18781
|
+
window.handleAgentStoreDemoRepositorySelect =
|
|
18782
|
+
handleAgentStoreDemoRepositorySelect;
|
|
18783
|
+
window.handleAgentStoreDemoUrlKeyDown = handleAgentStoreDemoUrlKeyDown;
|
|
18784
|
+
window.submitAgentStoreDemo = submitAgentStoreDemo;
|
|
18785
|
+
window.openAgentStoreDemoSessionSetup = openAgentStoreDemoSessionSetup;
|
|
18786
|
+
|
|
18221
18787
|
function getHostDefaultRuntime() {
|
|
18222
18788
|
return (
|
|
18223
18789
|
hostPreferencesState.effectiveDefaultProvider ||
|
|
@@ -29060,6 +29626,7 @@ Waiting for Claude login output…</pre
|
|
|
29060
29626
|
setVisible("browser-site-next-btn", isBrowser);
|
|
29061
29627
|
setVisible("browser-site-share-btn", isBrowser);
|
|
29062
29628
|
setVisible("browser-site-exit-btn", isBrowser);
|
|
29629
|
+
setVisible("entry-agent-store-btn", screen === "sessions");
|
|
29063
29630
|
setVisible("entry-start-session-btn", screen === "sessions");
|
|
29064
29631
|
setVisible("topbar-scheduler-new-btn", isSchedulerList);
|
|
29065
29632
|
setVisible("topbar-scheduler-refresh-btn", isSchedulerList);
|
|
@@ -29570,6 +30137,7 @@ Waiting for Claude login output…</pre
|
|
|
29570
30137
|
"browse-context-menu",
|
|
29571
30138
|
"picker-modal-backdrop",
|
|
29572
30139
|
"browse-create-folder-backdrop",
|
|
30140
|
+
"agent-store-demo-backdrop",
|
|
29573
30141
|
"provider-auth-backdrop",
|
|
29574
30142
|
"chat-image-preview-backdrop",
|
|
29575
30143
|
"command-palette",
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { lstat, realpath, rename, rm, stat } from "fs/promises";
|
|
4
|
+
import { join, relative } from "path";
|
|
5
|
+
import { resolveDefaultBrowsePathAsync } from "./config.mjs";
|
|
6
|
+
import { deriveBrowserRootProjectId } from "./browser-root-project-index.mjs";
|
|
7
|
+
|
|
8
|
+
export const AGENT_STORE_DEMO_CLONE_TIMEOUT_MS = Math.max(
|
|
9
|
+
1,
|
|
10
|
+
Number.parseInt(
|
|
11
|
+
process.env.OYSTERUN_AGENT_STORE_DEMO_CLONE_TIMEOUT_MS || "120000",
|
|
12
|
+
10
|
|
13
|
+
) || 120000
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const MAX_GIT_OUTPUT_BYTES = 64 * 1024;
|
|
17
|
+
const GIT_KILL_GRACE_MS = 1500;
|
|
18
|
+
const GITHUB_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
|
|
19
|
+
const GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
20
|
+
|
|
21
|
+
function createAgentStoreError(message, status, code, detail = "") {
|
|
22
|
+
const error = new Error(message);
|
|
23
|
+
error.status = status;
|
|
24
|
+
error.code = code;
|
|
25
|
+
if (detail) error.detail = detail;
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeString(value) {
|
|
30
|
+
return typeof value === "string" ? value.trim() : "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function appendBoundedOutput(current, chunk) {
|
|
34
|
+
if (current.length >= MAX_GIT_OUTPUT_BYTES) return current;
|
|
35
|
+
return `${current}${String(chunk)}`.slice(0, MAX_GIT_OUTPUT_BYTES);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function summarizeGitError(stderr, stdout) {
|
|
39
|
+
const value = normalizeString(stderr) || normalizeString(stdout);
|
|
40
|
+
return value.slice(0, 2000);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function pathExists(path) {
|
|
44
|
+
return Boolean(await lstat(path).catch(() => null));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertDirectChild(candidate, root) {
|
|
48
|
+
const relativePath = relative(root, candidate);
|
|
49
|
+
if (
|
|
50
|
+
!relativePath ||
|
|
51
|
+
relativePath.startsWith("..") ||
|
|
52
|
+
relativePath.startsWith("/") ||
|
|
53
|
+
relativePath.includes("/") ||
|
|
54
|
+
relativePath.includes("\\")
|
|
55
|
+
) {
|
|
56
|
+
throw createAgentStoreError(
|
|
57
|
+
"Agent Store destination must be a direct child of Default Browse Root",
|
|
58
|
+
400,
|
|
59
|
+
"agent_store_destination_invalid"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parsePublicGitHubRepositoryUrl(rawValue) {
|
|
65
|
+
const raw = normalizeString(rawValue);
|
|
66
|
+
if (!raw) {
|
|
67
|
+
throw createAgentStoreError(
|
|
68
|
+
"GitHub Repository URL is required",
|
|
69
|
+
400,
|
|
70
|
+
"invalid_github_repository_url"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = new URL(raw);
|
|
77
|
+
} catch {
|
|
78
|
+
throw createAgentStoreError(
|
|
79
|
+
"Enter a valid public GitHub HTTPS repository URL",
|
|
80
|
+
400,
|
|
81
|
+
"invalid_github_repository_url"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!/^https:\/\/github\.com\//i.test(raw)) {
|
|
86
|
+
throw createAgentStoreError(
|
|
87
|
+
"Only public https://github.com/<owner>/<repo> URLs are supported",
|
|
88
|
+
400,
|
|
89
|
+
"invalid_github_repository_url"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
parsed.protocol !== "https:" ||
|
|
95
|
+
parsed.hostname.toLowerCase() !== "github.com" ||
|
|
96
|
+
parsed.username ||
|
|
97
|
+
parsed.password ||
|
|
98
|
+
parsed.port ||
|
|
99
|
+
parsed.search ||
|
|
100
|
+
parsed.hash
|
|
101
|
+
) {
|
|
102
|
+
throw createAgentStoreError(
|
|
103
|
+
"Only public https://github.com/<owner>/<repo> URLs are supported",
|
|
104
|
+
400,
|
|
105
|
+
"invalid_github_repository_url"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+)\/?$/);
|
|
110
|
+
if (!match) {
|
|
111
|
+
throw createAgentStoreError(
|
|
112
|
+
"GitHub URL must contain exactly one owner and repository",
|
|
113
|
+
400,
|
|
114
|
+
"invalid_github_repository_url"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let owner;
|
|
119
|
+
let repositorySegment;
|
|
120
|
+
try {
|
|
121
|
+
owner = decodeURIComponent(match[1]);
|
|
122
|
+
repositorySegment = decodeURIComponent(match[2]);
|
|
123
|
+
} catch {
|
|
124
|
+
throw createAgentStoreError(
|
|
125
|
+
"GitHub URL contains invalid path encoding",
|
|
126
|
+
400,
|
|
127
|
+
"invalid_github_repository_url"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const repositoryName = repositorySegment.endsWith(".git")
|
|
131
|
+
? repositorySegment.slice(0, -4)
|
|
132
|
+
: repositorySegment;
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
!GITHUB_OWNER_PATTERN.test(owner) ||
|
|
136
|
+
!GITHUB_REPOSITORY_PATTERN.test(repositoryName) ||
|
|
137
|
+
repositoryName === "." ||
|
|
138
|
+
repositoryName === ".."
|
|
139
|
+
) {
|
|
140
|
+
throw createAgentStoreError(
|
|
141
|
+
"GitHub owner or repository name is invalid",
|
|
142
|
+
400,
|
|
143
|
+
"invalid_github_repository_url"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
owner,
|
|
149
|
+
repositoryName,
|
|
150
|
+
repositoryUrl: `https://github.com/${owner}/${repositoryName}`,
|
|
151
|
+
cloneUrl: `https://github.com/${owner}/${repositoryName}.git`,
|
|
152
|
+
agentId: deriveBrowserRootProjectId(repositoryName),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function terminateGitProcess(child, signal) {
|
|
157
|
+
if (!child) return;
|
|
158
|
+
if (process.platform !== "win32" && Number.isInteger(child.pid)) {
|
|
159
|
+
try {
|
|
160
|
+
process.kill(-child.pid, signal);
|
|
161
|
+
return;
|
|
162
|
+
} catch {
|
|
163
|
+
// Fall through to the direct child when the process group is unavailable.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
child.kill(signal);
|
|
168
|
+
} catch {
|
|
169
|
+
// The process may already have exited.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function runGitShallowClone({
|
|
174
|
+
cloneUrl,
|
|
175
|
+
destinationPath,
|
|
176
|
+
timeoutMs = AGENT_STORE_DEMO_CLONE_TIMEOUT_MS,
|
|
177
|
+
spawnImpl = spawn,
|
|
178
|
+
} = {}) {
|
|
179
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
180
|
+
let child;
|
|
181
|
+
try {
|
|
182
|
+
child = spawnImpl(
|
|
183
|
+
"git",
|
|
184
|
+
["clone", "--depth", "1", "--", cloneUrl, destinationPath],
|
|
185
|
+
{
|
|
186
|
+
detached: process.platform !== "win32",
|
|
187
|
+
env: process.env,
|
|
188
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
rejectPromise(
|
|
193
|
+
createAgentStoreError(
|
|
194
|
+
err?.code === "ENOENT"
|
|
195
|
+
? "Git is not available on this Host"
|
|
196
|
+
: "Could not start Git clone",
|
|
197
|
+
err?.code === "ENOENT" ? 503 : 502,
|
|
198
|
+
err?.code === "ENOENT" ? "git_unavailable" : "agent_store_clone_failed"
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let stdout = "";
|
|
205
|
+
let stderr = "";
|
|
206
|
+
let timedOut = false;
|
|
207
|
+
let settled = false;
|
|
208
|
+
let forceKillTimer = null;
|
|
209
|
+
|
|
210
|
+
const timeout = setTimeout(() => {
|
|
211
|
+
timedOut = true;
|
|
212
|
+
terminateGitProcess(child, "SIGTERM");
|
|
213
|
+
forceKillTimer = setTimeout(() => {
|
|
214
|
+
terminateGitProcess(child, "SIGKILL");
|
|
215
|
+
}, GIT_KILL_GRACE_MS);
|
|
216
|
+
}, timeoutMs);
|
|
217
|
+
|
|
218
|
+
const cleanup = () => {
|
|
219
|
+
clearTimeout(timeout);
|
|
220
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const rejectOnce = (error) => {
|
|
224
|
+
if (settled) return;
|
|
225
|
+
settled = true;
|
|
226
|
+
cleanup();
|
|
227
|
+
rejectPromise(error);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
child.stdout?.on("data", (chunk) => {
|
|
231
|
+
stdout = appendBoundedOutput(stdout, chunk);
|
|
232
|
+
});
|
|
233
|
+
child.stderr?.on("data", (chunk) => {
|
|
234
|
+
stderr = appendBoundedOutput(stderr, chunk);
|
|
235
|
+
});
|
|
236
|
+
child.once("error", (err) => {
|
|
237
|
+
rejectOnce(
|
|
238
|
+
createAgentStoreError(
|
|
239
|
+
err?.code === "ENOENT"
|
|
240
|
+
? "Git is not available on this Host"
|
|
241
|
+
: "Could not start Git clone",
|
|
242
|
+
err?.code === "ENOENT" ? 503 : 502,
|
|
243
|
+
err?.code === "ENOENT" ? "git_unavailable" : "agent_store_clone_failed"
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
child.once("close", (code, signal) => {
|
|
248
|
+
if (settled) return;
|
|
249
|
+
settled = true;
|
|
250
|
+
cleanup();
|
|
251
|
+
if (timedOut) {
|
|
252
|
+
rejectPromise(
|
|
253
|
+
createAgentStoreError(
|
|
254
|
+
"GitHub clone timed out",
|
|
255
|
+
504,
|
|
256
|
+
"agent_store_clone_timeout"
|
|
257
|
+
)
|
|
258
|
+
);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (code !== 0) {
|
|
262
|
+
rejectPromise(
|
|
263
|
+
createAgentStoreError(
|
|
264
|
+
"Could not clone the GitHub repository",
|
|
265
|
+
502,
|
|
266
|
+
"agent_store_clone_failed",
|
|
267
|
+
summarizeGitError(stderr, stdout) ||
|
|
268
|
+
`git exited with code ${code ?? "unknown"}${
|
|
269
|
+
signal ? ` (${signal})` : ""
|
|
270
|
+
}`
|
|
271
|
+
)
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
resolvePromise({ stdout, stderr, exitCode: code });
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function clonePublicGitHubAgent({
|
|
281
|
+
repositoryUrl,
|
|
282
|
+
browseRoot = null,
|
|
283
|
+
timeoutMs = AGENT_STORE_DEMO_CLONE_TIMEOUT_MS,
|
|
284
|
+
runClone = runGitShallowClone,
|
|
285
|
+
requestId = randomUUID(),
|
|
286
|
+
} = {}) {
|
|
287
|
+
const repository = parsePublicGitHubRepositoryUrl(repositoryUrl);
|
|
288
|
+
const requestedRoot = browseRoot || (await resolveDefaultBrowsePathAsync());
|
|
289
|
+
const rootPath = await realpath(requestedRoot);
|
|
290
|
+
const rootStats = await stat(rootPath);
|
|
291
|
+
if (!rootStats.isDirectory()) {
|
|
292
|
+
throw createAgentStoreError(
|
|
293
|
+
"Default Browse Root is not a directory",
|
|
294
|
+
400,
|
|
295
|
+
"agent_store_browse_root_invalid"
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const finalPath = join(rootPath, repository.repositoryName);
|
|
300
|
+
assertDirectChild(finalPath, rootPath);
|
|
301
|
+
if (await pathExists(finalPath)) {
|
|
302
|
+
throw createAgentStoreError(
|
|
303
|
+
"Agent folder already exists",
|
|
304
|
+
409,
|
|
305
|
+
"agent_folder_already_exists"
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const stagingName = `.oysterun-agent-store-${repository.agentId}-${requestId}.partial`;
|
|
310
|
+
const stagingPath = join(rootPath, stagingName);
|
|
311
|
+
assertDirectChild(stagingPath, rootPath);
|
|
312
|
+
if (await pathExists(stagingPath)) {
|
|
313
|
+
throw createAgentStoreError(
|
|
314
|
+
"Agent Store staging folder already exists",
|
|
315
|
+
409,
|
|
316
|
+
"agent_store_staging_conflict"
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let finalized = false;
|
|
321
|
+
try {
|
|
322
|
+
await runClone({
|
|
323
|
+
cloneUrl: repository.cloneUrl,
|
|
324
|
+
destinationPath: stagingPath,
|
|
325
|
+
timeoutMs,
|
|
326
|
+
});
|
|
327
|
+
const stagingStats = await stat(stagingPath).catch(() => null);
|
|
328
|
+
if (!stagingStats?.isDirectory()) {
|
|
329
|
+
throw createAgentStoreError(
|
|
330
|
+
"Git clone did not create an agent folder",
|
|
331
|
+
502,
|
|
332
|
+
"agent_store_clone_failed"
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (await pathExists(finalPath)) {
|
|
336
|
+
throw createAgentStoreError(
|
|
337
|
+
"Agent folder already exists",
|
|
338
|
+
409,
|
|
339
|
+
"agent_store_clone_finalize_conflict"
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
await rename(stagingPath, finalPath);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
if (["EEXIST", "ENOTEMPTY"].includes(err?.code)) {
|
|
346
|
+
throw createAgentStoreError(
|
|
347
|
+
"Agent folder already exists",
|
|
348
|
+
409,
|
|
349
|
+
"agent_store_clone_finalize_conflict"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
throw err;
|
|
353
|
+
}
|
|
354
|
+
finalized = true;
|
|
355
|
+
return {
|
|
356
|
+
status: "agent_store_demo_clone_complete",
|
|
357
|
+
repository_url: repository.repositoryUrl,
|
|
358
|
+
repository_owner: repository.owner,
|
|
359
|
+
repository_name: repository.repositoryName,
|
|
360
|
+
agent_id: repository.agentId,
|
|
361
|
+
agent_folder: await realpath(finalPath),
|
|
362
|
+
clone_depth: 1,
|
|
363
|
+
};
|
|
364
|
+
} finally {
|
|
365
|
+
if (!finalized) {
|
|
366
|
+
await rm(stagingPath, { recursive: true, force: true }).catch(() => {});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
package/host-service/server.mjs
CHANGED
|
@@ -117,6 +117,7 @@ import {
|
|
|
117
117
|
listFolderPage,
|
|
118
118
|
validateFolderPath,
|
|
119
119
|
} from "./folder-browser.mjs";
|
|
120
|
+
import { clonePublicGitHubAgent } from "./agent-store-demo.mjs";
|
|
120
121
|
import { attachRuntimeTurnId } from "./runtime-turn-events.mjs";
|
|
121
122
|
import { TerminalSessionManager } from "./terminal-session-manager.mjs";
|
|
122
123
|
import {
|
|
@@ -13454,6 +13455,44 @@ const server = createServer(async (req, res) => {
|
|
|
13454
13455
|
}
|
|
13455
13456
|
}
|
|
13456
13457
|
|
|
13458
|
+
if (req.method === "POST" && path === "/agent-store/demo/clone") {
|
|
13459
|
+
if (!requireDashboardMode(claims, res)) return;
|
|
13460
|
+
try {
|
|
13461
|
+
const body = await readBody(req);
|
|
13462
|
+
const payload = await clonePublicGitHubAgent({
|
|
13463
|
+
repositoryUrl: body.repository_url,
|
|
13464
|
+
});
|
|
13465
|
+
const snapshot = browserRootProjectIndex.refresh({
|
|
13466
|
+
reason: "agent_store_demo_clone",
|
|
13467
|
+
rebuildWatchers: true,
|
|
13468
|
+
});
|
|
13469
|
+
const discoveredProject = snapshot.projects.find(
|
|
13470
|
+
(project) => project.agent_folder === payload.agent_folder
|
|
13471
|
+
);
|
|
13472
|
+
return respond(res, 201, {
|
|
13473
|
+
...payload,
|
|
13474
|
+
project_discovery: discoveredProject
|
|
13475
|
+
? {
|
|
13476
|
+
status: "browser_root_project_discovered",
|
|
13477
|
+
project_id: discoveredProject.project_id,
|
|
13478
|
+
agent_folder: discoveredProject.agent_folder,
|
|
13479
|
+
}
|
|
13480
|
+
: {
|
|
13481
|
+
status: "browser_root_folder_unmarked",
|
|
13482
|
+
project_id: payload.agent_id,
|
|
13483
|
+
agent_folder: payload.agent_folder,
|
|
13484
|
+
},
|
|
13485
|
+
});
|
|
13486
|
+
} catch (err) {
|
|
13487
|
+
const response = {
|
|
13488
|
+
error: err.message || "Could not clone the GitHub repository",
|
|
13489
|
+
code: err.code || "agent_store_clone_failed",
|
|
13490
|
+
};
|
|
13491
|
+
if (err.detail) response.detail = err.detail;
|
|
13492
|
+
return respond(res, err.status || 500, response);
|
|
13493
|
+
}
|
|
13494
|
+
}
|
|
13495
|
+
|
|
13457
13496
|
if (req.method === "POST" && path === "/dev/open-permissions-settings") {
|
|
13458
13497
|
if (!requireDashboardMode(claims, res)) return;
|
|
13459
13498
|
if (!supportsMacFolderAccessPermissions()) {
|