@epoch-agent/server 0.2.0 → 0.3.2
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/README.md +204 -76
- package/dist/index.d.ts +376 -31
- package/dist/index.js +523 -16
- package/dist/web/assets/index-DDRS10ig.css +1 -0
- package/dist/web/assets/index-uicxKHU-.js +373 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/index-Bj3SdU3h.js +0 -366
- package/dist/web/assets/index-cRd3MeBE.css +0 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
|
-
import { t, ApprovalRelay, QuestionRelay, serializeStream, withRoleScope, withModelScope, withToolScope, nextRunAt, skillIndexDescription, SANDBOX_EXCLUDES, SANDBOX_COVERS, backgroundTaskOutput, listBackgroundTasks, providerLabel, MAX_REASON_CHARS, MAX_OBJECTIVE_CHARS, MAX_GOAL_MAX_ROUNDS, DEFAULT_GOAL_MAX_ROUNDS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_DEFAULTS, readRecording } from '@epoch-agent/runtime';
|
|
2
|
+
import { t, ApprovalRelay, QuestionRelay, serializeStream, withRoleScope, withModelScope, withToolScope, nextRunAt, skillIndexDescription, SANDBOX_EXCLUDES, SANDBOX_COVERS, isSystemOpenable, backgroundTaskOutput, listBackgroundTasks, providerLabel, MAX_REASON_CHARS, MAX_OBJECTIVE_CHARS, MAX_GOAL_MAX_ROUNDS, DEFAULT_GOAL_MAX_ROUNDS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_DEFAULTS, readRecording } from '@epoch-agent/runtime';
|
|
3
3
|
import { timingSafeEqual, randomBytes } from 'crypto';
|
|
4
|
-
import { PLAN_OUTCOMES, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, extractAllMentions, MAX_ATTACH_FILES, utf8ByteLength, MAX_SESSION_REFS, attachSessionSurface, isWireRewindScope, WIRE_REWIND_SCOPES, MAX_ATTACH_TOTAL_BYTES, MAX_ATTACH_BYTES, WIRE_LANG_PARAM, isLang, isQuestionAnswerMap, isProviderType, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS, collectPendingApprovals, unfixedRules, isWireSettingWriteLayer, parseModelRef, WIRE_GOAL_ACTIONS, OPERATION_TYPES, isOperationType } from '@epoch-agent/protocol';
|
|
4
|
+
import { PLAN_OUTCOMES, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, extractAllMentions, MAX_ATTACH_FILES, utf8ByteLength, MAX_SESSION_REFS, attachSessionSurface, isWireRewindScope, WIRE_REWIND_SCOPES, MAX_ATTACH_TOTAL_BYTES, MAX_ATTACH_BYTES, WIRE_LANG_PARAM, isLang, isQuestionAnswerMap, isProviderType, apiKeyEnvVar, WIRE_FILE_STAT_MAX, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS, collectPendingApprovals, unfixedRules, isWireSettingWriteLayer, parseModelRef, WIRE_GOAL_ACTIONS, OPERATION_TYPES, isOperationType } from '@epoch-agent/protocol';
|
|
5
5
|
import { resolve, join, relative, isAbsolute, sep, dirname, basename, extname } from 'path';
|
|
6
6
|
import { existsSync, realpathSync, statSync, mkdirSync, createReadStream, readdirSync, accessSync, constants } from 'fs';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
+
import { execFile, spawn } from 'child_process';
|
|
8
9
|
import { stat, readFile } from 'fs/promises';
|
|
9
|
-
import { execFile } from 'child_process';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
@@ -714,6 +714,10 @@ function sendJson(res, status, body, extra = {}) {
|
|
|
714
714
|
});
|
|
715
715
|
res.end(payload);
|
|
716
716
|
}
|
|
717
|
+
function sendEmpty(res, status, extra = {}) {
|
|
718
|
+
res.writeHead(status, { ...BASE_HEADERS, ...extra });
|
|
719
|
+
res.end();
|
|
720
|
+
}
|
|
717
721
|
function sendError(res, status, code, message, extra = {}) {
|
|
718
722
|
sendJson(res, status, { error: { code, message } }, extra);
|
|
719
723
|
}
|
|
@@ -1325,7 +1329,9 @@ function toWireSettingRow(row) {
|
|
|
1325
1329
|
layer: row.layer,
|
|
1326
1330
|
chain: row.chain.map(toWireSettingStep),
|
|
1327
1331
|
overridable: row.overridable,
|
|
1328
|
-
writes: row.writes.map(toWireSettingWrite)
|
|
1332
|
+
writes: row.writes.map(toWireSettingWrite),
|
|
1333
|
+
...row.valueKind === void 0 ? {} : { valueKind: row.valueKind },
|
|
1334
|
+
...row.choices === void 0 ? {} : { choices: [...row.choices] }
|
|
1329
1335
|
};
|
|
1330
1336
|
}
|
|
1331
1337
|
function toWireSettingStep(step) {
|
|
@@ -1442,6 +1448,7 @@ function config(ctx, res, lang) {
|
|
|
1442
1448
|
compression: runtime.compression,
|
|
1443
1449
|
...runtime.config.budget?.maxCostUsd !== void 0 ? { maxCostUsd: runtime.config.budget.maxCostUsd } : {},
|
|
1444
1450
|
...runtime.config.brand !== void 0 ? { brand: runtime.config.brand } : {},
|
|
1451
|
+
...runtime.config.sidebarMenu !== void 0 ? { sidebarMenu: runtime.config.sidebarMenu } : {},
|
|
1445
1452
|
provider: runtime.providerInfo,
|
|
1446
1453
|
tools: runtime.tools,
|
|
1447
1454
|
diagnostics: (lang && runtime.diagnosticsIn?.(lang)) ?? runtime.diagnosticList,
|
|
@@ -1453,7 +1460,8 @@ function config(ctx, res, lang) {
|
|
|
1453
1460
|
dbPath: runtime.config.dbPath,
|
|
1454
1461
|
...runtime.config.profile !== "default" ? { profile: runtime.config.profile } : {}
|
|
1455
1462
|
},
|
|
1456
|
-
lanExposed: ctx.lanExposed
|
|
1463
|
+
lanExposed: ctx.lanExposed,
|
|
1464
|
+
nativeDirPicker: ctx.nativeDirPicker
|
|
1457
1465
|
});
|
|
1458
1466
|
}
|
|
1459
1467
|
function listSessions(ctx, res, query, _lang) {
|
|
@@ -1532,7 +1540,11 @@ function listMessages(ctx, res, sessionId, lang) {
|
|
|
1532
1540
|
return sendUnknownSession(res, sessionId, lang);
|
|
1533
1541
|
}
|
|
1534
1542
|
const messages = ctx.runtime.sessionStore?.loadMessages(sessionId) ?? [];
|
|
1535
|
-
|
|
1543
|
+
const usage = ctx.runtime.sessionUsage(sessionId);
|
|
1544
|
+
sendJson(res, 200, {
|
|
1545
|
+
messages,
|
|
1546
|
+
...usage ? { usage } : {}
|
|
1547
|
+
});
|
|
1536
1548
|
}
|
|
1537
1549
|
async function postMessage(ctx, req, res, sessionId, lang) {
|
|
1538
1550
|
if (!ctx.hub.has(sessionId)) {
|
|
@@ -1683,13 +1695,13 @@ function getTools(ctx, res, sessionId, lang) {
|
|
|
1683
1695
|
sendJson(res, 200, payload);
|
|
1684
1696
|
}
|
|
1685
1697
|
function getSettings(ctx, res, sessionId, lang) {
|
|
1686
|
-
if (!ctx.hub.has(sessionId)
|
|
1698
|
+
if (!ctx.hub.has(sessionId)) {
|
|
1687
1699
|
return sendUnknownSession(res, sessionId, lang);
|
|
1688
1700
|
}
|
|
1689
1701
|
sendJson(res, 200, collectSettings(ctx.runtime, sessionId));
|
|
1690
1702
|
}
|
|
1691
1703
|
async function writeSetting(ctx, req, res, sessionId, lang) {
|
|
1692
|
-
if (!ctx.hub.has(sessionId)
|
|
1704
|
+
if (!ctx.hub.has(sessionId)) {
|
|
1693
1705
|
return sendUnknownSession(res, sessionId, lang);
|
|
1694
1706
|
}
|
|
1695
1707
|
const body = await readJsonBody(req, void 0, lang);
|
|
@@ -2382,9 +2394,14 @@ async function setPermission(ctx, req, res, sessionId, lang) {
|
|
|
2382
2394
|
);
|
|
2383
2395
|
}
|
|
2384
2396
|
const before = perms.level();
|
|
2385
|
-
const result = perms.setLevel(level);
|
|
2397
|
+
const result = perms.setLevel(level, { remember: true });
|
|
2386
2398
|
const state = permissionState(ctx, sessionId);
|
|
2387
|
-
const payload = result.ok ? {
|
|
2399
|
+
const payload = result.ok ? {
|
|
2400
|
+
ok: true,
|
|
2401
|
+
changed: state.level !== before,
|
|
2402
|
+
state,
|
|
2403
|
+
...result.notRemembered ? { notRemembered: result.notRemembered } : {}
|
|
2404
|
+
} : { ok: false, refused: REFUSAL[result.reason], changed: false, state };
|
|
2388
2405
|
sendJson(res, 200, payload);
|
|
2389
2406
|
}
|
|
2390
2407
|
// src/plan.ts
|
|
@@ -2440,6 +2457,15 @@ function toWirePluginHit(hit, installedNames) {
|
|
|
2440
2457
|
installed: installedNames.has(hit.entry.name)
|
|
2441
2458
|
};
|
|
2442
2459
|
}
|
|
2460
|
+
function toWireMarketplace(market) {
|
|
2461
|
+
return {
|
|
2462
|
+
name: market.name,
|
|
2463
|
+
source: market.source,
|
|
2464
|
+
entries: market.entries,
|
|
2465
|
+
addedAt: market.addedAt,
|
|
2466
|
+
local: market.local
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2443
2469
|
function toWirePreview2(preview) {
|
|
2444
2470
|
const { manifest, source, inventory } = preview;
|
|
2445
2471
|
return {
|
|
@@ -2492,6 +2518,7 @@ function listPlugins(ctx, res, lang) {
|
|
|
2492
2518
|
sendJson(res, 200, {
|
|
2493
2519
|
installed: installed.map(toWirePluginEntry),
|
|
2494
2520
|
hits: control.search().map((hit) => toWirePluginHit(hit, installedNames)),
|
|
2521
|
+
marketplaces: control.marketplaces().map(toWireMarketplace),
|
|
2495
2522
|
pendingRestart: control.pendingRestart
|
|
2496
2523
|
});
|
|
2497
2524
|
}
|
|
@@ -2521,6 +2548,14 @@ async function previewPluginInstall(ctx, req, res, lang) {
|
|
|
2521
2548
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2522
2549
|
sendPreview(res, await control.preview(fieldOf(asRecord(body.value), "ref"), lang));
|
|
2523
2550
|
}
|
|
2551
|
+
async function previewLocalPluginInstall(ctx, req, res, lang) {
|
|
2552
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2553
|
+
const control = controlOf(ctx, res, lang);
|
|
2554
|
+
if (!control) return;
|
|
2555
|
+
const body = await readJsonBody(req);
|
|
2556
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2557
|
+
sendPreview(res, await control.previewLocal(fieldOf(asRecord(body.value), "path"), lang));
|
|
2558
|
+
}
|
|
2524
2559
|
async function previewPluginUpdate(ctx, req, res, lang) {
|
|
2525
2560
|
if (refusedByLan(ctx, res, lang)) return;
|
|
2526
2561
|
const control = controlOf(ctx, res, lang);
|
|
@@ -2561,6 +2596,19 @@ async function installPluginFromMarket(ctx, req, res, lang) {
|
|
|
2561
2596
|
await control.install(fieldOf(payload, "ref"), fieldOf(payload, "token"), lang)
|
|
2562
2597
|
);
|
|
2563
2598
|
}
|
|
2599
|
+
async function installLocalPlugin(ctx, req, res, lang) {
|
|
2600
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2601
|
+
const control = controlOf(ctx, res, lang);
|
|
2602
|
+
if (!control) return;
|
|
2603
|
+
const body = await readJsonBody(req);
|
|
2604
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2605
|
+
const payload = asRecord(body.value);
|
|
2606
|
+
sendAction(
|
|
2607
|
+
res,
|
|
2608
|
+
control,
|
|
2609
|
+
await control.installLocal(fieldOf(payload, "path"), fieldOf(payload, "token"), lang)
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2564
2612
|
async function updateInstalledPlugin(ctx, req, res, lang) {
|
|
2565
2613
|
if (refusedByLan(ctx, res, lang)) return;
|
|
2566
2614
|
const control = controlOf(ctx, res, lang);
|
|
@@ -2582,12 +2630,37 @@ async function uninstallPluginByName(ctx, req, res, lang) {
|
|
|
2582
2630
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2583
2631
|
sendAction(res, control, await control.uninstall(fieldOf(asRecord(body.value), "name"), lang));
|
|
2584
2632
|
}
|
|
2633
|
+
function sendMarket(res, outcome) {
|
|
2634
|
+
sendJson(res, 200, {
|
|
2635
|
+
ok: outcome.ok,
|
|
2636
|
+
marketplace: outcome.ok && outcome.marketplace ? toWireMarketplace(outcome.marketplace) : null,
|
|
2637
|
+
reason: outcome.ok ? null : outcome.reason,
|
|
2638
|
+
detail: outcome.ok ? null : outcome.detail
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
async function addPluginMarketplace(ctx, req, res, lang) {
|
|
2642
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2643
|
+
const control = controlOf(ctx, res, lang);
|
|
2644
|
+
if (!control) return;
|
|
2645
|
+
const body = await readJsonBody(req);
|
|
2646
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2647
|
+
sendMarket(res, await control.addMarketplace(fieldOf(asRecord(body.value), "source"), lang));
|
|
2648
|
+
}
|
|
2649
|
+
async function removePluginMarketplace(ctx, req, res, lang) {
|
|
2650
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2651
|
+
const control = controlOf(ctx, res, lang);
|
|
2652
|
+
if (!control) return;
|
|
2653
|
+
const body = await readJsonBody(req);
|
|
2654
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2655
|
+
sendMarket(res, await control.removeMarketplace(fieldOf(asRecord(body.value), "name"), lang));
|
|
2656
|
+
}
|
|
2585
2657
|
function toWireProvider(option, lang) {
|
|
2586
2658
|
return {
|
|
2587
2659
|
type: option.type,
|
|
2588
2660
|
label: providerLabel(option, lang),
|
|
2589
2661
|
envVar: option.envVar,
|
|
2590
|
-
hasKey: option.hasKey
|
|
2662
|
+
hasKey: option.hasKey,
|
|
2663
|
+
keyHint: option.keyHint
|
|
2591
2664
|
};
|
|
2592
2665
|
}
|
|
2593
2666
|
function toWireSuggestions(view) {
|
|
@@ -2615,6 +2688,36 @@ async function discoverProviderModels(ctx, req, res, type, lang) {
|
|
|
2615
2688
|
suggestions: toWireSuggestions(suggestions)
|
|
2616
2689
|
});
|
|
2617
2690
|
}
|
|
2691
|
+
async function setProviderKey(ctx, req, res, type, lang) {
|
|
2692
|
+
if (!isProviderType(type)) {
|
|
2693
|
+
return sendError(res, 404, "unknown-provider", t("web.provider_unknown", { name: type }, lang));
|
|
2694
|
+
}
|
|
2695
|
+
const envVar = apiKeyEnvVar(type);
|
|
2696
|
+
if (envVar === null) {
|
|
2697
|
+
return sendError(
|
|
2698
|
+
res,
|
|
2699
|
+
400,
|
|
2700
|
+
"key-not-applicable",
|
|
2701
|
+
t("web.provider_key_not_applicable", { name: type }, lang)
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
const body = await readJsonBody(req);
|
|
2705
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2706
|
+
const raw = asRecord(body.value)["apiKey"];
|
|
2707
|
+
const apiKey = typeof raw === "string" ? raw.trim() : "";
|
|
2708
|
+
if (apiKey === "") {
|
|
2709
|
+
return sendError(res, 400, "empty-key", t("web.provider_key_empty", void 0, lang));
|
|
2710
|
+
}
|
|
2711
|
+
const written = await ctx.runtime.modelCatalog.setKey(type, apiKey);
|
|
2712
|
+
sendJson(res, 200, {
|
|
2713
|
+
provider: type,
|
|
2714
|
+
hasKey: true,
|
|
2715
|
+
keyHint: written.keyHint,
|
|
2716
|
+
backend: written.backend,
|
|
2717
|
+
encrypted: written.encrypted,
|
|
2718
|
+
envPath: written.envPath
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2618
2721
|
async function addRole(ctx, req, res, lang) {
|
|
2619
2722
|
if (ctx.lanExposed) {
|
|
2620
2723
|
return sendError(
|
|
@@ -3014,7 +3117,7 @@ async function previewSkillImport(ctx, req, res, lang) {
|
|
|
3014
3117
|
const body = await readJsonBody(req);
|
|
3015
3118
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
3016
3119
|
const source = pathOf(asRecord(body.value));
|
|
3017
|
-
const outcome = await ctx.runtime.
|
|
3120
|
+
const outcome = await ctx.runtime.skillWrite.preview(source, lang);
|
|
3018
3121
|
if (!outcome.ok) {
|
|
3019
3122
|
return sendJson(res, 200, {
|
|
3020
3123
|
ok: false,
|
|
@@ -3045,7 +3148,7 @@ async function importSkills(ctx, req, res, lang) {
|
|
|
3045
3148
|
const payload = asRecord(body.value);
|
|
3046
3149
|
const source = pathOf(payload);
|
|
3047
3150
|
const token = typeof payload["token"] === "string" ? payload["token"] : "";
|
|
3048
|
-
const outcome = await ctx.runtime.
|
|
3151
|
+
const outcome = await ctx.runtime.skillWrite.import(source, token, lang);
|
|
3049
3152
|
if (!outcome.ok) {
|
|
3050
3153
|
return sendJson(res, 200, {
|
|
3051
3154
|
ok: false,
|
|
@@ -3063,6 +3166,30 @@ async function importSkills(ctx, req, res, lang) {
|
|
|
3063
3166
|
reason: null
|
|
3064
3167
|
});
|
|
3065
3168
|
}
|
|
3169
|
+
// src/skill-remove.ts
|
|
3170
|
+
async function removeSkill(ctx, req, res, lang) {
|
|
3171
|
+
const body = await readJsonBody(req);
|
|
3172
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
3173
|
+
const payload = asRecord(body.value);
|
|
3174
|
+
const name = typeof payload["name"] === "string" ? payload["name"].trim() : "";
|
|
3175
|
+
const outcome = await ctx.runtime.skillWrite.remove(name, lang);
|
|
3176
|
+
if (!outcome.ok) {
|
|
3177
|
+
return sendJson(res, 200, {
|
|
3178
|
+
ok: false,
|
|
3179
|
+
name: "",
|
|
3180
|
+
path: null,
|
|
3181
|
+
detail: outcome.detail,
|
|
3182
|
+
reason: outcome.reason
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
sendJson(res, 200, {
|
|
3186
|
+
ok: true,
|
|
3187
|
+
name: outcome.name,
|
|
3188
|
+
path: outcome.path,
|
|
3189
|
+
detail: null,
|
|
3190
|
+
reason: null
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3066
3193
|
// src/sse.ts
|
|
3067
3194
|
var DEFAULT_HEARTBEAT_MS = 15e3;
|
|
3068
3195
|
function parseLastEventId(req) {
|
|
@@ -3134,6 +3261,9 @@ function getTasks(ctx, res, sessionId, _lang, registry = LIVE_TASKS) {
|
|
|
3134
3261
|
sendJson(res, 200, collectTasks(sessionId, registry));
|
|
3135
3262
|
}
|
|
3136
3263
|
var DIR_PAGE_LIMIT = 1e3;
|
|
3264
|
+
function workspaceHome() {
|
|
3265
|
+
return homedir();
|
|
3266
|
+
}
|
|
3137
3267
|
function workspaceAnchors(workspaces) {
|
|
3138
3268
|
const seen = /* @__PURE__ */ new Set();
|
|
3139
3269
|
const entries = [];
|
|
@@ -3227,10 +3357,127 @@ function parentOf(path) {
|
|
|
3227
3357
|
function displayName2(path) {
|
|
3228
3358
|
return basename(path) || path;
|
|
3229
3359
|
}
|
|
3360
|
+
function present(value) {
|
|
3361
|
+
return value !== void 0 && value !== "";
|
|
3362
|
+
}
|
|
3363
|
+
function resolveNativeDirPicker(facts) {
|
|
3364
|
+
return isSameMachine(facts);
|
|
3365
|
+
}
|
|
3366
|
+
function isSameMachine(facts) {
|
|
3367
|
+
if (facts.lanExposed) return false;
|
|
3368
|
+
if (present(facts.env.SSH_CONNECTION) || present(facts.env.SSH_TTY)) return false;
|
|
3369
|
+
return facts.platform === "darwin" || facts.platform === "win32";
|
|
3370
|
+
}
|
|
3371
|
+
function nativePickerCommands(platform, prompt) {
|
|
3372
|
+
if (platform === "darwin") {
|
|
3373
|
+
return [
|
|
3374
|
+
{
|
|
3375
|
+
command: "osascript",
|
|
3376
|
+
args: [
|
|
3377
|
+
"-e",
|
|
3378
|
+
`set epochChosenFolder to choose folder with prompt ${asAppleScriptString(prompt)}`,
|
|
3379
|
+
"-e",
|
|
3380
|
+
"POSIX path of epochChosenFolder"
|
|
3381
|
+
]
|
|
3382
|
+
}
|
|
3383
|
+
];
|
|
3384
|
+
}
|
|
3385
|
+
if (platform === "win32") {
|
|
3386
|
+
const script = win32PickScript(prompt);
|
|
3387
|
+
return [
|
|
3388
|
+
{ command: "pwsh", args: ["-NoProfile", "-STA", "-Command", script] },
|
|
3389
|
+
{ command: "powershell.exe", args: ["-NoProfile", "-STA", "-Command", script] }
|
|
3390
|
+
];
|
|
3391
|
+
}
|
|
3392
|
+
return null;
|
|
3393
|
+
}
|
|
3394
|
+
function win32PickScript(prompt) {
|
|
3395
|
+
return [
|
|
3396
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
3397
|
+
"$d = New-Object System.Windows.Forms.FolderBrowserDialog",
|
|
3398
|
+
`$d.Description = ${asPowerShellString(prompt)}`,
|
|
3399
|
+
"$d.ShowNewFolderButton = $true",
|
|
3400
|
+
"if ($d.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit 1 }",
|
|
3401
|
+
"[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($d.SelectedPath))"
|
|
3402
|
+
].join("; ");
|
|
3403
|
+
}
|
|
3404
|
+
function asAppleScriptString(text) {
|
|
3405
|
+
return `"${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
3406
|
+
}
|
|
3407
|
+
function asPowerShellString(text) {
|
|
3408
|
+
return `'${text.replace(/'/g, "''")}'`;
|
|
3409
|
+
}
|
|
3410
|
+
function decodeNativePick(platform, stdout) {
|
|
3411
|
+
const raw = stdout.trim();
|
|
3412
|
+
if (platform !== "win32") return raw;
|
|
3413
|
+
return Buffer.from(raw, "base64").toString("utf-8").trim();
|
|
3414
|
+
}
|
|
3415
|
+
function isPickCanceled(platform, code, stderr) {
|
|
3416
|
+
if (code !== 1) return false;
|
|
3417
|
+
if (platform === "win32") return true;
|
|
3418
|
+
return /-128|user canceled/i.test(stderr);
|
|
3419
|
+
}
|
|
3420
|
+
var inFlight = false;
|
|
3421
|
+
async function pickNativeDirectory(opts) {
|
|
3422
|
+
const platform = opts.platform ?? process.platform;
|
|
3423
|
+
const candidates = nativePickerCommands(platform, opts.prompt);
|
|
3424
|
+
if (!candidates) {
|
|
3425
|
+
return { ok: false, reason: "unsupported", message: `unsupported platform: ${platform}` };
|
|
3426
|
+
}
|
|
3427
|
+
if (inFlight) {
|
|
3428
|
+
return { ok: false, reason: "busy", message: "a directory picker is already open" };
|
|
3429
|
+
}
|
|
3430
|
+
const run = opts.runner ?? execFileRunner;
|
|
3431
|
+
inFlight = true;
|
|
3432
|
+
try {
|
|
3433
|
+
let last = "";
|
|
3434
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
3435
|
+
const attempt = await run(candidate.command, candidate.args, opts.signal);
|
|
3436
|
+
if (attempt.missing) {
|
|
3437
|
+
last = attempt.message || `${candidate.command} not found`;
|
|
3438
|
+
if (index < candidates.length - 1) continue;
|
|
3439
|
+
return { ok: false, reason: "failed", message: last };
|
|
3440
|
+
}
|
|
3441
|
+
if (!attempt.code) {
|
|
3442
|
+
const path = decodeNativePick(platform, attempt.stdout);
|
|
3443
|
+
return path ? { ok: true, path } : { ok: false, reason: "failed", message: "picker returned an empty path" };
|
|
3444
|
+
}
|
|
3445
|
+
if (isPickCanceled(platform, attempt.code, attempt.stderr)) {
|
|
3446
|
+
return { ok: true, canceled: true };
|
|
3447
|
+
}
|
|
3448
|
+
return { ok: false, reason: "failed", message: attempt.stderr.trim() || attempt.message };
|
|
3449
|
+
}
|
|
3450
|
+
return { ok: false, reason: "failed", message: last };
|
|
3451
|
+
} finally {
|
|
3452
|
+
inFlight = false;
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
var execFileRunner = (command, args, signal) => new Promise((done) => {
|
|
3456
|
+
execFile(
|
|
3457
|
+
command,
|
|
3458
|
+
[...args],
|
|
3459
|
+
{
|
|
3460
|
+
...signal ? { signal } : {},
|
|
3461
|
+
encoding: "utf-8",
|
|
3462
|
+
windowsHide: true,
|
|
3463
|
+
maxBuffer: 1024 * 1024
|
|
3464
|
+
},
|
|
3465
|
+
(error, stdout, stderr) => {
|
|
3466
|
+
const err = error;
|
|
3467
|
+
done({
|
|
3468
|
+
code: err?.code ?? 0,
|
|
3469
|
+
stdout,
|
|
3470
|
+
stderr,
|
|
3471
|
+
missing: err?.code === "ENOENT",
|
|
3472
|
+
message: err?.message ?? ""
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
);
|
|
3476
|
+
});
|
|
3230
3477
|
// src/workspace/dirs.ts
|
|
3231
3478
|
function browseWorkspaceDirs(ctx, res, rawUrl, lang) {
|
|
3232
3479
|
const query = new URL(rawUrl ?? "/", "http://127.0.0.1").searchParams;
|
|
3233
|
-
const path = dirsQuery(query);
|
|
3480
|
+
const path = dirsQuery(query) ?? atQuery(query);
|
|
3234
3481
|
if (path === void 0) {
|
|
3235
3482
|
return sendJson(res, 200, workspaceAnchors(ctx.runtime.workspaces));
|
|
3236
3483
|
}
|
|
@@ -3260,6 +3507,9 @@ function dirsQuery(query) {
|
|
|
3260
3507
|
const value = query.get("path")?.trim();
|
|
3261
3508
|
return value ? value : void 0;
|
|
3262
3509
|
}
|
|
3510
|
+
function atQuery(query) {
|
|
3511
|
+
return query.get("at")?.trim().toLowerCase() === "home" ? workspaceHome() : void 0;
|
|
3512
|
+
}
|
|
3263
3513
|
function hiddenQuery(query) {
|
|
3264
3514
|
const value = query.get("hidden")?.trim().toLowerCase();
|
|
3265
3515
|
return value === "1" || value === "true";
|
|
@@ -3297,6 +3547,34 @@ async function createWorkspaceDir(req, res, lang) {
|
|
|
3297
3547
|
}
|
|
3298
3548
|
});
|
|
3299
3549
|
}
|
|
3550
|
+
async function pickWorkspaceDir(ctx, req, res, lang) {
|
|
3551
|
+
if (!ctx.nativeDirPicker) {
|
|
3552
|
+
return sendError(res, 403, "native-picker-unavailable", t("web.pick_unavailable", {}, lang));
|
|
3553
|
+
}
|
|
3554
|
+
const abort = new AbortController();
|
|
3555
|
+
req.on("close", () => abort.abort());
|
|
3556
|
+
const outcome = await pickNativeDirectory({
|
|
3557
|
+
prompt: t("web.pick_prompt", {}, lang),
|
|
3558
|
+
signal: abort.signal
|
|
3559
|
+
});
|
|
3560
|
+
if (res.writableEnded || abort.signal.aborted) return;
|
|
3561
|
+
if (outcome.ok) {
|
|
3562
|
+
return sendJson(
|
|
3563
|
+
res,
|
|
3564
|
+
200,
|
|
3565
|
+
outcome.path === void 0 ? { canceled: true } : { path: outcome.path }
|
|
3566
|
+
);
|
|
3567
|
+
}
|
|
3568
|
+
if (outcome.reason === "busy") {
|
|
3569
|
+
return sendError(res, 409, "native-picker-busy", t("web.pick_busy", {}, lang));
|
|
3570
|
+
}
|
|
3571
|
+
sendError(
|
|
3572
|
+
res,
|
|
3573
|
+
500,
|
|
3574
|
+
"native-picker-failed",
|
|
3575
|
+
t("web.pick_failed", { detail: outcome.message }, lang)
|
|
3576
|
+
);
|
|
3577
|
+
}
|
|
3300
3578
|
function isSingleSegment(name) {
|
|
3301
3579
|
if (name.length === 0) return false;
|
|
3302
3580
|
if (name === "." || name === "..") return false;
|
|
@@ -3311,6 +3589,195 @@ function mkdirFailure(cause, path, lang) {
|
|
|
3311
3589
|
if (code === "ENOENT") return [404, "not-found", t("web.mkdir_parent_missing", { path }, lang)];
|
|
3312
3590
|
return [500, "internal", t("web.mkdir_failed", { path }, lang)];
|
|
3313
3591
|
}
|
|
3592
|
+
var MAX_TEXT_BYTES = 2 * 1024 * 1024;
|
|
3593
|
+
var VIEWABLE = /* @__PURE__ */ new Map([
|
|
3594
|
+
[".png", { kind: "image", mime: "image/png" }],
|
|
3595
|
+
[".jpg", { kind: "image", mime: "image/jpeg" }],
|
|
3596
|
+
[".jpeg", { kind: "image", mime: "image/jpeg" }],
|
|
3597
|
+
[".gif", { kind: "image", mime: "image/gif" }],
|
|
3598
|
+
[".webp", { kind: "image", mime: "image/webp" }],
|
|
3599
|
+
[".bmp", { kind: "image", mime: "image/bmp" }],
|
|
3600
|
+
[".ico", { kind: "image", mime: "image/x-icon" }],
|
|
3601
|
+
[".avif", { kind: "image", mime: "image/avif" }],
|
|
3602
|
+
[".pdf", { kind: "pdf", mime: "application/pdf" }],
|
|
3603
|
+
[".mp3", { kind: "audio", mime: "audio/mpeg" }],
|
|
3604
|
+
[".wav", { kind: "audio", mime: "audio/wav" }],
|
|
3605
|
+
[".ogg", { kind: "audio", mime: "audio/ogg" }],
|
|
3606
|
+
[".m4a", { kind: "audio", mime: "audio/mp4" }],
|
|
3607
|
+
[".flac", { kind: "audio", mime: "audio/flac" }],
|
|
3608
|
+
[".mp4", { kind: "video", mime: "video/mp4" }],
|
|
3609
|
+
[".webm", { kind: "video", mime: "video/webm" }],
|
|
3610
|
+
[".mov", { kind: "video", mime: "video/quicktime" }]
|
|
3611
|
+
]);
|
|
3612
|
+
function kindOf(stat2) {
|
|
3613
|
+
if (stat2.kind === "text") return "text";
|
|
3614
|
+
return VIEWABLE.get(extname(stat2.rel).toLowerCase())?.kind ?? "opaque";
|
|
3615
|
+
}
|
|
3616
|
+
function toWireRefusal(reason) {
|
|
3617
|
+
return reason;
|
|
3618
|
+
}
|
|
3619
|
+
function statusFor(refusal) {
|
|
3620
|
+
if (refusal === "denied") return 403;
|
|
3621
|
+
if (refusal === "missing") return 404;
|
|
3622
|
+
if (refusal === "too-large") return 413;
|
|
3623
|
+
return refusal === "not-a-file" ? 400 : 503;
|
|
3624
|
+
}
|
|
3625
|
+
function describeRefusal(refusal, lang) {
|
|
3626
|
+
if (refusal === "denied") return t("web.file_view_denied", void 0, lang);
|
|
3627
|
+
if (refusal === "missing") return t("web.file_view_missing", void 0, lang);
|
|
3628
|
+
if (refusal === "not-a-file") return t("web.file_view_not_a_file", void 0, lang);
|
|
3629
|
+
if (refusal === "too-large") {
|
|
3630
|
+
return t("web.file_view_too_large", { max: String(MAX_TEXT_BYTES) }, lang);
|
|
3631
|
+
}
|
|
3632
|
+
return t("web.file_view_unreadable", void 0, lang);
|
|
3633
|
+
}
|
|
3634
|
+
function resolveViewRoot(ctx, res, sessionId, lang) {
|
|
3635
|
+
if (!ctx.hub.has(sessionId) && !ctx.runtime.sessions?.get(sessionId)) {
|
|
3636
|
+
sendUnknownSession(res, sessionId);
|
|
3637
|
+
return null;
|
|
3638
|
+
}
|
|
3639
|
+
const root = workspaceRootOf(ctx, sessionId);
|
|
3640
|
+
if (!root) {
|
|
3641
|
+
sendError(res, 409, "no-workspace", t("web.file_view_no_workspace", void 0, lang));
|
|
3642
|
+
return null;
|
|
3643
|
+
}
|
|
3644
|
+
return root;
|
|
3645
|
+
}
|
|
3646
|
+
function singlePath(res, rawUrl, lang) {
|
|
3647
|
+
const params = new URLSearchParams((rawUrl ?? "").split("?")[1] ?? "");
|
|
3648
|
+
const path = params.get("path")?.trim();
|
|
3649
|
+
if (!path) {
|
|
3650
|
+
sendError(res, 400, "missing-path", t("web.file_view_missing_path", void 0, lang));
|
|
3651
|
+
return null;
|
|
3652
|
+
}
|
|
3653
|
+
return path;
|
|
3654
|
+
}
|
|
3655
|
+
function statFiles(ctx, res, sessionId, rawUrl, lang) {
|
|
3656
|
+
const root = resolveViewRoot(ctx, res, sessionId, lang);
|
|
3657
|
+
if (root === null) return;
|
|
3658
|
+
const params = new URLSearchParams((rawUrl ?? "").split("?")[1] ?? "");
|
|
3659
|
+
const asked = params.getAll("path").filter((p) => p.trim() !== "");
|
|
3660
|
+
const taken = asked.slice(0, WIRE_FILE_STAT_MAX);
|
|
3661
|
+
const files = taken.map((path) => {
|
|
3662
|
+
const stat2 = ctx.runtime.workspaceFiles.statFile(path, root);
|
|
3663
|
+
if (!stat2.ok) return { path, ok: false, refusal: toWireRefusal(stat2.reason) };
|
|
3664
|
+
return {
|
|
3665
|
+
path,
|
|
3666
|
+
ok: true,
|
|
3667
|
+
kind: kindOf(stat2),
|
|
3668
|
+
bytes: stat2.bytes,
|
|
3669
|
+
mtimeMs: stat2.mtimeMs,
|
|
3670
|
+
openable: ctx.sameMachine && isSystemOpenable(stat2.rel)
|
|
3671
|
+
};
|
|
3672
|
+
});
|
|
3673
|
+
sendJson(res, 200, {
|
|
3674
|
+
files,
|
|
3675
|
+
overLimit: asked.length - taken.length,
|
|
3676
|
+
root
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
function readFileText(ctx, res, sessionId, rawUrl, lang) {
|
|
3680
|
+
const root = resolveViewRoot(ctx, res, sessionId, lang);
|
|
3681
|
+
if (root === null) return;
|
|
3682
|
+
const path = singlePath(res, rawUrl, lang);
|
|
3683
|
+
if (path === null) return;
|
|
3684
|
+
const stat2 = ctx.runtime.workspaceFiles.statFile(path, root);
|
|
3685
|
+
if (!stat2.ok) {
|
|
3686
|
+
const refusal = toWireRefusal(stat2.reason);
|
|
3687
|
+
return sendError(res, statusFor(refusal), refusal, describeRefusal(refusal, lang));
|
|
3688
|
+
}
|
|
3689
|
+
if (stat2.kind !== "text") {
|
|
3690
|
+
return sendError(res, 415, "not-text", t("web.file_view_not_text", void 0, lang));
|
|
3691
|
+
}
|
|
3692
|
+
if (stat2.bytes > MAX_TEXT_BYTES) {
|
|
3693
|
+
return sendError(res, 413, "too-large", describeRefusal("too-large", lang));
|
|
3694
|
+
}
|
|
3695
|
+
const out = ctx.runtime.workspaceFiles.readFile(stat2.rel, root);
|
|
3696
|
+
if (!out.ok) {
|
|
3697
|
+
const refusal = out.reason === "binary" ? "unreadable" : out.reason;
|
|
3698
|
+
return sendError(res, statusFor(refusal), refusal, describeRefusal(refusal, lang));
|
|
3699
|
+
}
|
|
3700
|
+
sendJson(res, 200, {
|
|
3701
|
+
path,
|
|
3702
|
+
text: out.text,
|
|
3703
|
+
bytes: out.bytes
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3706
|
+
function readFileBytes(ctx, res, sessionId, rawUrl, headOnly, lang) {
|
|
3707
|
+
const root = resolveViewRoot(ctx, res, sessionId, lang);
|
|
3708
|
+
if (root === null) return;
|
|
3709
|
+
const path = singlePath(res, rawUrl, lang);
|
|
3710
|
+
if (path === null) return;
|
|
3711
|
+
const stat2 = ctx.runtime.workspaceFiles.statFile(path, root);
|
|
3712
|
+
if (!stat2.ok) {
|
|
3713
|
+
const refusal = toWireRefusal(stat2.reason);
|
|
3714
|
+
return sendError(res, statusFor(refusal), refusal, describeRefusal(refusal, lang));
|
|
3715
|
+
}
|
|
3716
|
+
const known = VIEWABLE.get(extname(stat2.rel).toLowerCase());
|
|
3717
|
+
const inline = known !== void 0 && stat2.kind === "binary";
|
|
3718
|
+
const headers = {
|
|
3719
|
+
"Content-Type": inline ? known.mime : "application/octet-stream",
|
|
3720
|
+
"X-Content-Type-Options": "nosniff",
|
|
3721
|
+
"Content-Disposition": inline ? "inline" : `attachment; filename*=UTF-8''${encodeURIComponent(basenameOf(stat2.rel))}`
|
|
3722
|
+
};
|
|
3723
|
+
if (inline && known.kind === "pdf") headers["Content-Security-Policy"] = "sandbox";
|
|
3724
|
+
if (headOnly) {
|
|
3725
|
+
return sendEmpty(res, 200, {
|
|
3726
|
+
...headers,
|
|
3727
|
+
"Content-Length": String(stat2.bytes)
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
sendFile(res, stat2.absPath, headers);
|
|
3731
|
+
}
|
|
3732
|
+
function basenameOf(rel) {
|
|
3733
|
+
const cut = rel.lastIndexOf("/");
|
|
3734
|
+
return cut < 0 ? rel : rel.slice(cut + 1);
|
|
3735
|
+
}
|
|
3736
|
+
// src/workspace/file-open.ts
|
|
3737
|
+
function describeOpenRefusal(refusal, lang) {
|
|
3738
|
+
if (refusal === "extension-not-allowed") {
|
|
3739
|
+
return t("web.file_open_extension_not_allowed", void 0, lang);
|
|
3740
|
+
}
|
|
3741
|
+
if (refusal === "not-same-machine") return t("web.file_open_not_same_machine", void 0, lang);
|
|
3742
|
+
if (refusal === "launch-failed") return t("web.file_open_launch_failed", void 0, lang);
|
|
3743
|
+
return describeRefusal(refusal, lang);
|
|
3744
|
+
}
|
|
3745
|
+
function refuse(res, refusal, lang) {
|
|
3746
|
+
sendJson(res, 200, {
|
|
3747
|
+
ok: false,
|
|
3748
|
+
refusal,
|
|
3749
|
+
message: describeOpenRefusal(refusal, lang)
|
|
3750
|
+
});
|
|
3751
|
+
}
|
|
3752
|
+
async function openFileWithSystemApp(ctx, req, res, sessionId, lang) {
|
|
3753
|
+
if (!ctx.sameMachine) {
|
|
3754
|
+
return sendError(res, 403, "not-same-machine", describeOpenRefusal("not-same-machine", lang));
|
|
3755
|
+
}
|
|
3756
|
+
const root = resolveViewRoot(ctx, res, sessionId, lang);
|
|
3757
|
+
if (root === null) return;
|
|
3758
|
+
const body = await readJsonBody(req);
|
|
3759
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
3760
|
+
const payload = asRecord(body.value);
|
|
3761
|
+
const path = typeof payload["path"] === "string" ? payload["path"].trim() : "";
|
|
3762
|
+
if (path === "") {
|
|
3763
|
+
return sendError(res, 400, "missing-path", t("web.file_view_missing_path", void 0, lang));
|
|
3764
|
+
}
|
|
3765
|
+
const plan = ctx.runtime.workspaceFiles.planSystemOpen(path, root);
|
|
3766
|
+
if (!plan.ok) return refuse(res, plan.reason, lang);
|
|
3767
|
+
try {
|
|
3768
|
+
const child = spawn(plan.command, [...plan.args], { stdio: "ignore", detached: true });
|
|
3769
|
+
await new Promise((done, fail) => {
|
|
3770
|
+
child.on("error", fail);
|
|
3771
|
+
child.on("spawn", () => {
|
|
3772
|
+
child.unref();
|
|
3773
|
+
done();
|
|
3774
|
+
});
|
|
3775
|
+
});
|
|
3776
|
+
} catch {
|
|
3777
|
+
return refuse(res, "launch-failed", lang);
|
|
3778
|
+
}
|
|
3779
|
+
sendJson(res, 200, { ok: true });
|
|
3780
|
+
}
|
|
3314
3781
|
var GIT_TIMEOUT_MS = 1e4;
|
|
3315
3782
|
var GIT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
3316
3783
|
function runGit(cwd, args) {
|
|
@@ -3765,6 +4232,9 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
3765
4232
|
if (resource === "skills" && first === "import" && second === void 0 && method === "POST") {
|
|
3766
4233
|
return importSkills(ctx, req, res, lang);
|
|
3767
4234
|
}
|
|
4235
|
+
if (resource === "skills" && first === "remove" && second === void 0 && method === "POST") {
|
|
4236
|
+
return removeSkill(ctx, req, res, lang);
|
|
4237
|
+
}
|
|
3768
4238
|
if (resource === "plugins" && first === void 0 && isRead(method)) {
|
|
3769
4239
|
return listPlugins(ctx, res, lang);
|
|
3770
4240
|
}
|
|
@@ -3774,6 +4244,12 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
3774
4244
|
if (resource === "plugins" && first === "install" && second === void 0 && method === "POST") {
|
|
3775
4245
|
return installPluginFromMarket(ctx, req, res, lang);
|
|
3776
4246
|
}
|
|
4247
|
+
if (resource === "plugins" && first === "install" && second === "local" && detail === "preview" && method === "POST") {
|
|
4248
|
+
return previewLocalPluginInstall(ctx, req, res, lang);
|
|
4249
|
+
}
|
|
4250
|
+
if (resource === "plugins" && first === "install" && second === "local" && detail === void 0 && method === "POST") {
|
|
4251
|
+
return installLocalPlugin(ctx, req, res, lang);
|
|
4252
|
+
}
|
|
3777
4253
|
if (resource === "plugins" && first === "update" && second === "preview" && method === "POST") {
|
|
3778
4254
|
return previewPluginUpdate(ctx, req, res, lang);
|
|
3779
4255
|
}
|
|
@@ -3783,18 +4259,30 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
3783
4259
|
if (resource === "plugins" && first === "uninstall" && second === void 0 && method === "POST") {
|
|
3784
4260
|
return uninstallPluginByName(ctx, req, res, lang);
|
|
3785
4261
|
}
|
|
4262
|
+
if (resource === "plugins" && first === "marketplaces" && second === "remove" && method === "POST") {
|
|
4263
|
+
return removePluginMarketplace(ctx, req, res, lang);
|
|
4264
|
+
}
|
|
4265
|
+
if (resource === "plugins" && first === "marketplaces" && second === void 0 && method === "POST") {
|
|
4266
|
+
return addPluginMarketplace(ctx, req, res, lang);
|
|
4267
|
+
}
|
|
3786
4268
|
if (resource === "providers" && first === void 0 && isRead(method)) {
|
|
3787
4269
|
return listProviders(ctx, res, lang);
|
|
3788
4270
|
}
|
|
3789
4271
|
if (resource === "providers" && first !== void 0 && second === "models" && method === "POST") {
|
|
3790
4272
|
return discoverProviderModels(ctx, req, res, first, lang);
|
|
3791
4273
|
}
|
|
4274
|
+
if (resource === "providers" && first !== void 0 && second === "key" && method === "POST") {
|
|
4275
|
+
return setProviderKey(ctx, req, res, first, lang);
|
|
4276
|
+
}
|
|
3792
4277
|
if (resource === "workspaces" && first === "dirs" && isRead(method)) {
|
|
3793
4278
|
return browseWorkspaceDirs(ctx, res, req.url, lang);
|
|
3794
4279
|
}
|
|
3795
4280
|
if (resource === "workspaces" && first === void 0 && method === "POST") {
|
|
3796
4281
|
return createWorkspaceDir(req, res, lang);
|
|
3797
4282
|
}
|
|
4283
|
+
if (resource === "workspaces" && first === "pick" && method === "POST") {
|
|
4284
|
+
return pickWorkspaceDir(ctx, req, res, lang);
|
|
4285
|
+
}
|
|
3798
4286
|
if (resource === "schedules") {
|
|
3799
4287
|
return handleSchedules(ctx, req, res, method, first, second, detail, tail, lang);
|
|
3800
4288
|
}
|
|
@@ -3862,6 +4350,18 @@ async function handleSessions(opts, req, res, method, sessionId, action, detail,
|
|
|
3862
4350
|
if (action === "file-candidates" && isRead(method)) {
|
|
3863
4351
|
return listFileCandidates(ctx, res, sessionId, req.url, lang);
|
|
3864
4352
|
}
|
|
4353
|
+
if (action === "file-stat" && isRead(method)) {
|
|
4354
|
+
return statFiles(ctx, res, sessionId, req.url, lang);
|
|
4355
|
+
}
|
|
4356
|
+
if (action === "file" && isRead(method)) {
|
|
4357
|
+
return readFileText(ctx, res, sessionId, req.url, lang);
|
|
4358
|
+
}
|
|
4359
|
+
if (action === "file-bytes" && isRead(method)) {
|
|
4360
|
+
return readFileBytes(ctx, res, sessionId, req.url, method === "HEAD", lang);
|
|
4361
|
+
}
|
|
4362
|
+
if (action === "file-open" && method === "POST") {
|
|
4363
|
+
return openFileWithSystemApp(ctx, req, res, sessionId, lang);
|
|
4364
|
+
}
|
|
3865
4365
|
if (action === "approvals" && isRead(method)) return listApprovals(ctx, res, sessionId, lang);
|
|
3866
4366
|
if (action === "questions" && isRead(method)) return listQuestions(ctx, res, sessionId, lang);
|
|
3867
4367
|
if (action === "capabilities" && isRead(method))
|
|
@@ -4016,13 +4516,20 @@ async function createWebServer(opts) {
|
|
|
4016
4516
|
};
|
|
4017
4517
|
}
|
|
4018
4518
|
function buildHandler(opts, hub, binding, port, webRoot) {
|
|
4519
|
+
const machineFacts = {
|
|
4520
|
+
lanExposed: binding.lanExposed,
|
|
4521
|
+
platform: process.platform,
|
|
4522
|
+
env: process.env
|
|
4523
|
+
};
|
|
4019
4524
|
return createRequestHandler({
|
|
4020
4525
|
ctx: {
|
|
4021
4526
|
hub,
|
|
4022
4527
|
runtime: opts.runtime,
|
|
4023
4528
|
version: opts.version,
|
|
4024
4529
|
artifactsRoot: opts.artifactsRoot ?? opts.runtime.artifactsRoot,
|
|
4025
|
-
lanExposed: binding.lanExposed
|
|
4530
|
+
lanExposed: binding.lanExposed,
|
|
4531
|
+
nativeDirPicker: resolveNativeDirPicker(machineFacts),
|
|
4532
|
+
sameMachine: isSameMachine(machineFacts)
|
|
4026
4533
|
},
|
|
4027
4534
|
guard: createAuthGuard({ token: binding.token, port, lanExposed: binding.lanExposed }),
|
|
4028
4535
|
...webRoot === void 0 ? {} : { webRoot },
|
|
@@ -4068,4 +4575,4 @@ function makeCloser(server, hub, runtime) {
|
|
|
4068
4575
|
};
|
|
4069
4576
|
}
|
|
4070
4577
|
|
|
4071
|
-
export { DEFAULT_MAX_ACTIVE_SESSIONS, DEFAULT_MAX_QUEUED_MESSAGES, DEFAULT_RING_CAPACITY, DEFAULT_WEB_HOST, DEFAULT_WEB_PORT, EnvelopeRing, MAX_CANDIDATES, MAX_DIFF_BYTES, MAX_DIFF_FILES, MAX_RECORDING_FRAMES, MAX_SKILL_BODY_BYTES, SessionHub, TASK_TAIL_BYTES, UI_LANG_PARAM, UI_THEME_PARAM, attachMentions, collectCapabilities, collectSecurity, collectSettings, collectTasks, collectWorkspaceDiff, createAuthGuard, createWebServer, decideBinding, defaultWebRoot, expandUserContent, firstScreenUrl, generateToken, listFileCandidates, listReferenceCandidates, readRewindInput, resolveArtifactPath, toWireCheckpoint, toWireCommand, toWirePluginEntry, toWirePreview, toWireRewindResult, toWireRow, toWireSkillBody, toWireTask, unavailableToHttp };
|
|
4578
|
+
export { DEFAULT_MAX_ACTIVE_SESSIONS, DEFAULT_MAX_QUEUED_MESSAGES, DEFAULT_RING_CAPACITY, DEFAULT_WEB_HOST, DEFAULT_WEB_PORT, EnvelopeRing, MAX_CANDIDATES, MAX_DIFF_BYTES, MAX_DIFF_FILES, MAX_RECORDING_FRAMES, MAX_SKILL_BODY_BYTES, SessionHub, TASK_TAIL_BYTES, UI_LANG_PARAM, UI_THEME_PARAM, attachMentions, collectCapabilities, collectSecurity, collectSettings, collectTasks, collectWorkspaceDiff, createAuthGuard, createWebServer, decideBinding, defaultWebRoot, expandUserContent, firstScreenUrl, generateToken, isSameMachine, listFileCandidates, listReferenceCandidates, readRewindInput, resolveArtifactPath, resolveNativeDirPicker, toWireCheckpoint, toWireCommand, toWirePluginEntry, toWirePreview, toWireRewindResult, toWireRow, toWireSkillBody, toWireTask, unavailableToHttp };
|