@geminixiang/mikan 1.0.0-beta.94 → 1.0.0-beta.95
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/CHANGELOG.md +15 -0
- package/dist/adapters/commands/login.js +1 -1
- package/dist/adapters/commands/login.js.map +1 -1
- package/dist/adapters/commands/registry.d.ts +1 -1
- package/dist/adapters/commands/registry.d.ts.map +1 -1
- package/dist/adapters/commands/registry.js +1 -2
- package/dist/adapters/commands/registry.js.map +1 -1
- package/dist/adapters/commands/types.d.ts +2 -4
- package/dist/adapters/commands/types.d.ts.map +1 -1
- package/dist/adapters/commands/types.js.map +1 -1
- package/dist/adapters/web/admin/portal.d.ts.map +1 -1
- package/dist/adapters/web/admin/portal.js +92 -173
- package/dist/adapters/web/admin/portal.js.map +1 -1
- package/dist/adapters/web/admin/types.d.ts +3 -8
- package/dist/adapters/web/admin/types.d.ts.map +1 -1
- package/dist/adapters/web/admin/types.js.map +1 -1
- package/dist/adapters/web/server.d.ts +1 -1
- package/dist/adapters/web/server.d.ts.map +1 -1
- package/dist/adapters/web/server.js.map +1 -1
- package/dist/cli/arg-grammar.d.ts +0 -1
- package/dist/cli/arg-grammar.d.ts.map +1 -1
- package/dist/cli/arg-grammar.js +0 -3
- package/dist/cli/arg-grammar.js.map +1 -1
- package/dist/env-manifest.d.ts +0 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +0 -4
- package/dist/env-manifest.js.map +1 -1
- package/dist/harness/execution-resolver.js +1 -1
- package/dist/harness/execution-resolver.js.map +1 -1
- package/dist/harness/models.d.ts +2 -2
- package/dist/harness/models.d.ts.map +1 -1
- package/dist/harness/models.js +4 -6
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/runner.js +1 -2
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/types.d.ts +1 -1
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/main.js +3 -5
- package/dist/main.js.map +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +0 -6
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/session-v3.d.ts +57 -0
- package/dist/migrations/session-v3.d.ts.map +1 -0
- package/dist/migrations/session-v3.js +93 -0
- package/dist/migrations/session-v3.js.map +1 -0
- package/dist/migrations/sessions-sqlite.d.ts.map +1 -1
- package/dist/migrations/sessions-sqlite.js +98 -54
- package/dist/migrations/sessions-sqlite.js.map +1 -1
- package/dist/observability/instrument.js +1 -3
- package/dist/observability/instrument.js.map +1 -1
- package/dist/runtime/conversation-runtime.js +9 -14
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +2 -7
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/settings/apply.d.ts +1 -1
- package/dist/settings/apply.d.ts.map +1 -1
- package/dist/settings/apply.js +4 -5
- package/dist/settings/apply.js.map +1 -1
- package/dist/settings/index.d.ts +4 -4
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +21 -20
- package/dist/settings/index.js.map +1 -1
- package/dist/types.d.ts +2 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +1 -14
- package/dist/vault/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/migrations/dream-checkpoints.d.ts +0 -3
- package/dist/migrations/dream-checkpoints.d.ts.map +0 -1
- package/dist/migrations/dream-checkpoints.js +0 -23
- package/dist/migrations/dream-checkpoints.js.map +0 -1
- package/dist/migrations/private-office-dirs.d.ts +0 -3
- package/dist/migrations/private-office-dirs.d.ts.map +0 -1
- package/dist/migrations/private-office-dirs.js +0 -24
- package/dist/migrations/private-office-dirs.js.map +0 -1
- package/dist/migrations/session-files.d.ts +0 -28
- package/dist/migrations/session-files.d.ts.map +0 -1
- package/dist/migrations/session-files.js +0 -80
- package/dist/migrations/session-files.js.map +0 -1
- package/dist/migrations/session-v4.d.ts +0 -77
- package/dist/migrations/session-v4.d.ts.map +0 -1
- package/dist/migrations/session-v4.js +0 -177
- package/dist/migrations/session-v4.js.map +0 -1
- package/dist/migrations/sessions-v3.d.ts +0 -8
- package/dist/migrations/sessions-v3.d.ts.map +0 -1
- package/dist/migrations/sessions-v3.js +0 -350
- package/dist/migrations/sessions-v3.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { validateEventFilename } from "../../../events/index.js";
|
|
|
2
2
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, } from "node:fs";
|
|
3
3
|
import { basename, join, matchesGlob, resolve as pathResolve, sep as pathSep } from "node:path";
|
|
4
4
|
import { atomicWritePrivateFile } from "../../../file-guards.js";
|
|
5
|
-
import { MikanModels } from "../../../harness/models.js";
|
|
5
|
+
import { defaultModelsJsonPath, MikanModels } from "../../../harness/models.js";
|
|
6
6
|
import { formatSkillsForPrompt, loadOfficeSkills, resolveSkillEnabled, validateSkill, } from "../../../harness/skills.js";
|
|
7
7
|
import { SessionStore } from "../../../sessions/session-store.js";
|
|
8
8
|
import { InMemoryTokenStore } from "../token-store.js";
|
|
@@ -83,9 +83,9 @@ async function routeGetApiRequest(res, url, services) {
|
|
|
83
83
|
case "/admin/api/conversation-state":
|
|
84
84
|
return serveConversationState(res, url, services, token);
|
|
85
85
|
case "/admin/api/settings/global":
|
|
86
|
-
return serveGlobalSettings(res);
|
|
86
|
+
return serveGlobalSettings(res, services.workspace);
|
|
87
87
|
case "/admin/api/models":
|
|
88
|
-
return serveModelsList(res);
|
|
88
|
+
return serveModelsList(res, services.workspace);
|
|
89
89
|
case "/admin/api/workspace/tree":
|
|
90
90
|
return serveWorkspaceTree(res, url, services, token);
|
|
91
91
|
case "/admin/api/workspace/file":
|
|
@@ -162,21 +162,12 @@ function resolveTargetConversation(body, token) {
|
|
|
162
162
|
const platform = typeof body.platform === "string" ? body.platform.trim() : "";
|
|
163
163
|
return resolveConversationScope(requested, platform, token);
|
|
164
164
|
}
|
|
165
|
-
function
|
|
166
|
-
if (!services.workspace) {
|
|
167
|
-
jsonRes(res, 503, { error: "Working directory not available" });
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
return services.workspace;
|
|
171
|
-
}
|
|
172
|
-
function requireConversationWorkspace(res, body, services, token) {
|
|
173
|
-
const scope = resolveTargetConversation(body, token);
|
|
165
|
+
function scopedOffice(res, services, scope) {
|
|
174
166
|
if (scope.error) {
|
|
175
167
|
jsonRes(res, 403, { error: scope.error });
|
|
176
168
|
return undefined;
|
|
177
169
|
}
|
|
178
|
-
|
|
179
|
-
return workspace ? { scope, workspace } : undefined;
|
|
170
|
+
return services.workspace.office(scope.address);
|
|
180
171
|
}
|
|
181
172
|
function listAdminOffices(workspace) {
|
|
182
173
|
return listRegisteredOffices(workspace.stateDir)
|
|
@@ -224,9 +215,7 @@ function conversationDisplayLabel(services, office) {
|
|
|
224
215
|
return `${office.platform}:${office.conversationId}`;
|
|
225
216
|
}
|
|
226
217
|
function serveConversationsList(res, services) {
|
|
227
|
-
const workspace =
|
|
228
|
-
if (!workspace)
|
|
229
|
-
return;
|
|
218
|
+
const { workspace } = services;
|
|
230
219
|
const running = services.runtime?.getRunningSessions() ?? [];
|
|
231
220
|
const conversations = listAdminOffices(workspace).map((office) => ({
|
|
232
221
|
platform: office.platform,
|
|
@@ -238,9 +227,7 @@ function serveConversationsList(res, services) {
|
|
|
238
227
|
jsonRes(res, 200, { conversations });
|
|
239
228
|
}
|
|
240
229
|
async function serveSessionUsage(res, services) {
|
|
241
|
-
const workspace =
|
|
242
|
-
if (!workspace)
|
|
243
|
-
return;
|
|
230
|
+
const { workspace } = services;
|
|
244
231
|
const usageLists = [];
|
|
245
232
|
for (const office of listAdminOffices(workspace)) {
|
|
246
233
|
usageLists.push(await listConversationSessionUsage(workspace, office, conversationDisplayLabel(services, office)));
|
|
@@ -315,9 +302,7 @@ function emptyBucket(date) {
|
|
|
315
302
|
return { date, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, cost: 0 };
|
|
316
303
|
}
|
|
317
304
|
async function serveConversationUsage(res, url, services) {
|
|
318
|
-
const workspace =
|
|
319
|
-
if (!workspace)
|
|
320
|
-
return;
|
|
305
|
+
const { workspace } = services;
|
|
321
306
|
const conversationId = (url.searchParams.get("conversationId") ?? "").trim();
|
|
322
307
|
const platformParam = (url.searchParams.get("platform") ?? "").trim();
|
|
323
308
|
const office = listAdminOffices(workspace).find((candidate) => candidate.conversationId === conversationId &&
|
|
@@ -406,17 +391,12 @@ async function accumulateSessionUsageByDay(office, sessionKey, cutoff, buckets,
|
|
|
406
391
|
catch { }
|
|
407
392
|
}
|
|
408
393
|
function serveConversationState(res, url, services, token) {
|
|
409
|
-
const workspace =
|
|
410
|
-
|
|
394
|
+
const { workspace } = services;
|
|
395
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
396
|
+
if (!office)
|
|
411
397
|
return;
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
jsonRes(res, 400, { error: scope.error });
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
const conversationId = scope.conversationId;
|
|
418
|
-
const office = workspace.office(scope.address);
|
|
419
|
-
const globalConfig = loadGlobalSettings();
|
|
398
|
+
const conversationId = office.address.conversationId;
|
|
399
|
+
const globalConfig = loadGlobalSettings(workspace.stateDir);
|
|
420
400
|
const conversationConfig = resolveConversationSettings(office);
|
|
421
401
|
const conversationWorkspace = resolveWorkspaceProjection(office);
|
|
422
402
|
jsonRes(res, 200, {
|
|
@@ -436,9 +416,9 @@ function serveConversationState(res, url, services, token) {
|
|
|
436
416
|
},
|
|
437
417
|
});
|
|
438
418
|
}
|
|
439
|
-
function serveGlobalSettings(res) {
|
|
419
|
+
function serveGlobalSettings(res, workspace) {
|
|
440
420
|
try {
|
|
441
|
-
const config = loadGlobalSettings();
|
|
421
|
+
const config = loadGlobalSettings(workspace.stateDir);
|
|
442
422
|
jsonRes(res, 200, {
|
|
443
423
|
provider: config.provider,
|
|
444
424
|
model: config.model,
|
|
@@ -457,9 +437,11 @@ function serveGlobalSettings(res) {
|
|
|
457
437
|
jsonRes(res, 500, { error: errorMessage(err) });
|
|
458
438
|
}
|
|
459
439
|
}
|
|
460
|
-
async function serveModelsList(res) {
|
|
440
|
+
async function serveModelsList(res, workspace) {
|
|
461
441
|
try {
|
|
462
|
-
const registry = MikanModels.create(
|
|
442
|
+
const registry = MikanModels.create({
|
|
443
|
+
modelsJsonPath: defaultModelsJsonPath(workspace.stateDir),
|
|
444
|
+
});
|
|
463
445
|
const availableModels = await registry.getAvailable();
|
|
464
446
|
const statuses = await resolveAdminModelAccessStatuses(registry, availableModels);
|
|
465
447
|
const models = availableModels.map((model) => ({
|
|
@@ -478,24 +460,25 @@ async function serveModelsList(res) {
|
|
|
478
460
|
jsonRes(res, 500, { error: errorMessage(err) });
|
|
479
461
|
}
|
|
480
462
|
}
|
|
481
|
-
function
|
|
463
|
+
function readModelSelection(res, body) {
|
|
482
464
|
const provider = typeof body.provider === "string" ? body.provider.trim() : "";
|
|
483
465
|
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
484
|
-
const thinkingLevel = isThinkingLevel(body.thinkingLevel) ? body.thinkingLevel : undefined;
|
|
485
466
|
if (!provider || !model) {
|
|
486
467
|
jsonRes(res, 400, { error: "Missing provider or model" });
|
|
487
|
-
return;
|
|
468
|
+
return undefined;
|
|
488
469
|
}
|
|
489
|
-
const
|
|
490
|
-
|
|
470
|
+
const thinkingLevel = isThinkingLevel(body.thinkingLevel) ? body.thinkingLevel : undefined;
|
|
471
|
+
return { provider, model, thinkingLevel };
|
|
472
|
+
}
|
|
473
|
+
function serveConversationModelUpdate(res, body, services, token) {
|
|
474
|
+
const selection = readModelSelection(res, body);
|
|
475
|
+
if (!selection)
|
|
476
|
+
return;
|
|
477
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
478
|
+
if (!office)
|
|
491
479
|
return;
|
|
492
|
-
const { scope, workspace } = target;
|
|
493
480
|
try {
|
|
494
|
-
const result = applyConversationSettings(services.runtime,
|
|
495
|
-
provider,
|
|
496
|
-
model,
|
|
497
|
-
thinkingLevel,
|
|
498
|
-
});
|
|
481
|
+
const result = applyConversationSettings(services.runtime, office, selection);
|
|
499
482
|
if (!result.ok) {
|
|
500
483
|
jsonRes(res, 409, {
|
|
501
484
|
error: "Conversation has a running job; retry after it finishes (or /stop it).",
|
|
@@ -513,12 +496,11 @@ function serveConversationVisibilityUpdate(res, body, services, token) {
|
|
|
513
496
|
jsonRes(res, 400, { error: "visibility must be 'private' or 'default'" });
|
|
514
497
|
return;
|
|
515
498
|
}
|
|
516
|
-
const
|
|
517
|
-
if (!
|
|
499
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
500
|
+
if (!office)
|
|
518
501
|
return;
|
|
519
|
-
const { scope, workspace } = target;
|
|
520
502
|
try {
|
|
521
|
-
const result = applyOfficeVisibility(services.runtime,
|
|
503
|
+
const result = applyOfficeVisibility(services.runtime, office, body.visibility === "private" ? "private" : null);
|
|
522
504
|
if (!result.ok) {
|
|
523
505
|
jsonRes(res, 409, { error: "Conversation is busy; retry when the current run finishes" });
|
|
524
506
|
return;
|
|
@@ -535,22 +517,20 @@ function serveConversationSlackUpdate(res, body, services, token) {
|
|
|
535
517
|
jsonRes(res, 400, { error: "replyMode must be 'top-level' or 'thread'" });
|
|
536
518
|
return;
|
|
537
519
|
}
|
|
538
|
-
const
|
|
539
|
-
if (!
|
|
520
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
521
|
+
if (!office)
|
|
540
522
|
return;
|
|
541
|
-
const { scope, workspace } = target;
|
|
542
523
|
respondWithSettingsUpdate(res, () => {
|
|
543
|
-
applyConversationSettings(services.runtime,
|
|
524
|
+
applyConversationSettings(services.runtime, office, {
|
|
544
525
|
slack: { replyMode },
|
|
545
526
|
});
|
|
546
527
|
return { ok: true };
|
|
547
528
|
});
|
|
548
529
|
}
|
|
549
530
|
async function serveConversationSessionLink(res, body, services, token) {
|
|
550
|
-
const
|
|
551
|
-
if (!
|
|
531
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
532
|
+
if (!office)
|
|
552
533
|
return;
|
|
553
|
-
const { scope, workspace } = target;
|
|
554
534
|
if (!services.sessionViewTokenStore) {
|
|
555
535
|
jsonRes(res, 503, { error: "Session view token store not available" });
|
|
556
536
|
return;
|
|
@@ -561,8 +541,7 @@ async function serveConversationSessionLink(res, body, services, token) {
|
|
|
561
541
|
});
|
|
562
542
|
return;
|
|
563
543
|
}
|
|
564
|
-
|
|
565
|
-
if (!(await SessionStore.exists(office, scope.conversationId))) {
|
|
544
|
+
if (!(await SessionStore.exists(office, office.address.conversationId))) {
|
|
566
545
|
jsonRes(res, 404, { error: "No session found for this conversation" });
|
|
567
546
|
return;
|
|
568
547
|
}
|
|
@@ -570,8 +549,8 @@ async function serveConversationSessionLink(res, body, services, token) {
|
|
|
570
549
|
const { token: viewToken } = services.sessionViewTokenStore.create({
|
|
571
550
|
platform: token.platform,
|
|
572
551
|
platformUserId: token.platformUserId,
|
|
573
|
-
conversationId:
|
|
574
|
-
sessionKey:
|
|
552
|
+
conversationId: office.address.conversationId,
|
|
553
|
+
sessionKey: office.address.conversationId,
|
|
575
554
|
office,
|
|
576
555
|
platformUserName: token.platformUserName,
|
|
577
556
|
});
|
|
@@ -583,11 +562,9 @@ async function serveConversationSessionLink(res, body, services, token) {
|
|
|
583
562
|
}
|
|
584
563
|
}
|
|
585
564
|
function serveConversationLoginLink(res, body, services, token) {
|
|
586
|
-
const
|
|
587
|
-
if (
|
|
588
|
-
jsonRes(res, 403, { error: scope.error });
|
|
565
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
566
|
+
if (!office)
|
|
589
567
|
return;
|
|
590
|
-
}
|
|
591
568
|
if (!services.portalBaseUrl) {
|
|
592
569
|
jsonRes(res, 503, { error: "Portal URL not configured." });
|
|
593
570
|
return;
|
|
@@ -610,7 +587,7 @@ function serveConversationLoginLink(res, body, services, token) {
|
|
|
610
587
|
try {
|
|
611
588
|
vaultId = credentialAuthorizationKey(services.sandbox, {
|
|
612
589
|
userId: token.platformUserId,
|
|
613
|
-
address:
|
|
590
|
+
address: office.address,
|
|
614
591
|
});
|
|
615
592
|
}
|
|
616
593
|
catch (err) {
|
|
@@ -619,7 +596,7 @@ function serveConversationLoginLink(res, body, services, token) {
|
|
|
619
596
|
}
|
|
620
597
|
}
|
|
621
598
|
try {
|
|
622
|
-
const { token: linkToken } = services.linkTokenStore.create(token.platform, token.platformUserId,
|
|
599
|
+
const { token: linkToken } = services.linkTokenStore.create(token.platform, token.platformUserId, office.address.conversationId, vaultId, "");
|
|
623
600
|
const url = `${services.portalBaseUrl}/link?token=${encodeURIComponent(linkToken)}`;
|
|
624
601
|
jsonRes(res, 200, { ok: true, url, vaultId });
|
|
625
602
|
}
|
|
@@ -628,19 +605,11 @@ function serveConversationLoginLink(res, body, services, token) {
|
|
|
628
605
|
}
|
|
629
606
|
}
|
|
630
607
|
function serveGlobalModelUpdate(res, body, services) {
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
const thinkingLevel = isThinkingLevel(body.thinkingLevel) ? body.thinkingLevel : undefined;
|
|
634
|
-
if (!provider || !model) {
|
|
635
|
-
jsonRes(res, 400, { error: "Missing provider or model" });
|
|
608
|
+
const selection = readModelSelection(res, body);
|
|
609
|
+
if (!selection)
|
|
636
610
|
return;
|
|
637
|
-
}
|
|
638
611
|
respondWithSettingsUpdate(res, () => {
|
|
639
|
-
const result = applyGlobalSettings(services.runtime,
|
|
640
|
-
provider,
|
|
641
|
-
model,
|
|
642
|
-
thinkingLevel,
|
|
643
|
-
});
|
|
612
|
+
const result = applyGlobalSettings(services.runtime, services.workspace.stateDir, selection);
|
|
644
613
|
return {
|
|
645
614
|
ok: true,
|
|
646
615
|
staleConversations: result.staleConversations.map((office) => `${office.platform}:${office.conversationId}`),
|
|
@@ -654,7 +623,7 @@ function serveGlobalSlackUpdate(res, body, services) {
|
|
|
654
623
|
return;
|
|
655
624
|
}
|
|
656
625
|
respondWithSettingsUpdate(res, () => {
|
|
657
|
-
applyGlobalSettings(services.runtime, { slack: { replyMode } });
|
|
626
|
+
applyGlobalSettings(services.runtime, services.workspace.stateDir, { slack: { replyMode } });
|
|
658
627
|
return { ok: true };
|
|
659
628
|
});
|
|
660
629
|
}
|
|
@@ -680,7 +649,7 @@ function serveGlobalSandboxUpdate(res, body, services) {
|
|
|
680
649
|
return;
|
|
681
650
|
}
|
|
682
651
|
respondWithSettingsUpdate(res, () => {
|
|
683
|
-
applyGlobalSettings(services.runtime, { sandbox: update });
|
|
652
|
+
applyGlobalSettings(services.runtime, services.workspace.stateDir, { sandbox: update });
|
|
684
653
|
return { ok: true };
|
|
685
654
|
});
|
|
686
655
|
}
|
|
@@ -783,17 +752,12 @@ function buildTree(startDir, relPrefix) {
|
|
|
783
752
|
return node;
|
|
784
753
|
}
|
|
785
754
|
function serveWorkspaceTree(res, url, services, token) {
|
|
786
|
-
const
|
|
787
|
-
if (
|
|
788
|
-
jsonRes(res, 403, { error: scope.error });
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
792
|
-
if (!workspace)
|
|
755
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
756
|
+
if (!office)
|
|
793
757
|
return;
|
|
794
|
-
const convDir =
|
|
758
|
+
const convDir = office.dir;
|
|
795
759
|
if (!existsSync(convDir)) {
|
|
796
|
-
jsonRes(res, 200, { conversationId:
|
|
760
|
+
jsonRes(res, 200, { conversationId: office.address.conversationId, tree: null });
|
|
797
761
|
return;
|
|
798
762
|
}
|
|
799
763
|
const requestedSub = (url.searchParams.get("path") ?? "").trim();
|
|
@@ -808,7 +772,7 @@ function serveWorkspaceTree(res, url, services, token) {
|
|
|
808
772
|
}
|
|
809
773
|
const tree = buildTree(startSafe.absolute, requestedSub);
|
|
810
774
|
jsonRes(res, 200, {
|
|
811
|
-
conversationId:
|
|
775
|
+
conversationId: office.address.conversationId,
|
|
812
776
|
root: requestedSub || ".",
|
|
813
777
|
tree,
|
|
814
778
|
});
|
|
@@ -877,13 +841,8 @@ function servePreviewFile(res, absolutePath, metadata, notFoundMessage) {
|
|
|
877
841
|
});
|
|
878
842
|
}
|
|
879
843
|
function serveWorkspaceFile(res, url, services, token) {
|
|
880
|
-
const
|
|
881
|
-
if (
|
|
882
|
-
jsonRes(res, 403, { error: scope.error });
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
886
|
-
if (!workspace)
|
|
844
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
845
|
+
if (!office)
|
|
887
846
|
return;
|
|
888
847
|
const requestedPath = (url.searchParams.get("path") ?? "").trim();
|
|
889
848
|
if (!requestedPath) {
|
|
@@ -894,7 +853,7 @@ function serveWorkspaceFile(res, url, services, token) {
|
|
|
894
853
|
jsonRes(res, 403, { error: "Workspace path is not exposed" });
|
|
895
854
|
return;
|
|
896
855
|
}
|
|
897
|
-
const convDir =
|
|
856
|
+
const convDir = office.dir;
|
|
898
857
|
const safe = safeJoinUnderRoot(convDir, requestedPath);
|
|
899
858
|
if (safe.error) {
|
|
900
859
|
jsonRes(res, 400, { error: safe.error });
|
|
@@ -903,17 +862,12 @@ function serveWorkspaceFile(res, url, services, token) {
|
|
|
903
862
|
servePreviewFile(res, safe.absolute, { path: requestedPath }, "File not found");
|
|
904
863
|
}
|
|
905
864
|
function serveMcpServersList(res, url, services, token) {
|
|
906
|
-
const
|
|
907
|
-
if (
|
|
908
|
-
jsonRes(res, 403, { error: scope.error });
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
912
|
-
if (!workspace)
|
|
865
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
866
|
+
if (!office)
|
|
913
867
|
return;
|
|
914
|
-
const servers = loadScopeMcpServers(
|
|
868
|
+
const servers = loadScopeMcpServers(office);
|
|
915
869
|
jsonRes(res, 200, {
|
|
916
|
-
conversationId:
|
|
870
|
+
conversationId: office.address.conversationId,
|
|
917
871
|
presets: listMcpPresets(),
|
|
918
872
|
global: redactMcpServers(servers.global),
|
|
919
873
|
conversation: redactMcpServers(servers.conversation),
|
|
@@ -1017,15 +971,10 @@ async function serveMcpServerMutation(res, body, services, token) {
|
|
|
1017
971
|
return;
|
|
1018
972
|
}
|
|
1019
973
|
const mutationScope = body.scope === "global" ? "global" : "conversation";
|
|
1020
|
-
const
|
|
1021
|
-
if (
|
|
1022
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1026
|
-
if (!workspace)
|
|
974
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
975
|
+
if (!office)
|
|
1027
976
|
return;
|
|
1028
|
-
const
|
|
977
|
+
const { workspace } = services;
|
|
1029
978
|
const maps = loadScopeMcpServers(office);
|
|
1030
979
|
const current = mutationScope === "global" ? maps.global : maps.conversation;
|
|
1031
980
|
if (action === "test") {
|
|
@@ -1045,7 +994,7 @@ async function serveMcpServerMutation(res, body, services, token) {
|
|
|
1045
994
|
return;
|
|
1046
995
|
}
|
|
1047
996
|
const result = mutationScope === "global"
|
|
1048
|
-
? applyGlobalSettings(services.runtime, { mcpServers: plan.next })
|
|
997
|
+
? applyGlobalSettings(services.runtime, workspace.stateDir, { mcpServers: plan.next })
|
|
1049
998
|
: applyConversationSettings(services.runtime, office, { mcpServers: plan.next });
|
|
1050
999
|
if (!result.ok) {
|
|
1051
1000
|
jsonRes(res, 409, { error: "Conversation is busy; try again shortly." });
|
|
@@ -1105,36 +1054,27 @@ function officeSkillListing(workspace, office) {
|
|
|
1105
1054
|
};
|
|
1106
1055
|
}
|
|
1107
1056
|
function serveSkillsList(res, url, services, token) {
|
|
1108
|
-
const
|
|
1109
|
-
if (
|
|
1110
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1057
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
1058
|
+
if (!office)
|
|
1111
1059
|
return;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
if (!workspace)
|
|
1115
|
-
return;
|
|
1116
|
-
jsonRes(res, 200, officeSkillListing(workspace, workspace.office(scope.address)));
|
|
1060
|
+
const { workspace } = services;
|
|
1061
|
+
jsonRes(res, 200, officeSkillListing(workspace, office));
|
|
1117
1062
|
}
|
|
1118
1063
|
const SKILL_TOGGLE_STATES = {
|
|
1119
1064
|
global: ["load", "unload"],
|
|
1120
1065
|
conversation: ["inherit", "load", "unload"],
|
|
1121
1066
|
};
|
|
1122
1067
|
function serveSkillToggle(res, body, services, token) {
|
|
1123
|
-
const
|
|
1124
|
-
if (
|
|
1125
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1129
|
-
if (!workspace)
|
|
1068
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
1069
|
+
if (!office)
|
|
1130
1070
|
return;
|
|
1071
|
+
const { workspace } = services;
|
|
1131
1072
|
const target = body.scope === "global" || body.scope === "conversation" ? body.scope : undefined;
|
|
1132
1073
|
const state = typeof body.state === "string" ? body.state : "";
|
|
1133
1074
|
if (!target || !SKILL_TOGGLE_STATES[target].includes(state)) {
|
|
1134
1075
|
jsonRes(res, 400, { error: "Invalid scope or state" });
|
|
1135
1076
|
return;
|
|
1136
1077
|
}
|
|
1137
|
-
const office = workspace.office(scope.address);
|
|
1138
1078
|
const skill = officeSkillListing(workspace, office).skills.find((entry) => entry.source === body.source && entry.directory === body.directory);
|
|
1139
1079
|
if (!skill) {
|
|
1140
1080
|
jsonRes(res, 404, { error: "Skill not found" });
|
|
@@ -1143,18 +1083,14 @@ function serveSkillToggle(res, body, services, token) {
|
|
|
1143
1083
|
const patterns = loadSkillPatterns(office)[target].filter((pattern) => pattern.slice(1) !== skill.directory);
|
|
1144
1084
|
if (state !== "inherit")
|
|
1145
1085
|
patterns.push(`${state === "load" ? "+" : "-"}${skill.directory}`);
|
|
1146
|
-
updateSkillPatterns(
|
|
1086
|
+
updateSkillPatterns(office, target, patterns);
|
|
1147
1087
|
jsonRes(res, 200, officeSkillListing(workspace, office));
|
|
1148
1088
|
}
|
|
1149
1089
|
function serveSkillFile(res, url, services, token) {
|
|
1150
|
-
const
|
|
1151
|
-
if (
|
|
1152
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1156
|
-
if (!workspace)
|
|
1090
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
1091
|
+
if (!office)
|
|
1157
1092
|
return;
|
|
1093
|
+
const { workspace } = services;
|
|
1158
1094
|
const source = (url.searchParams.get("source") ?? "").trim();
|
|
1159
1095
|
const directory = (url.searchParams.get("directory") ?? "").trim();
|
|
1160
1096
|
if (source !== "global" && source !== "conversation") {
|
|
@@ -1168,7 +1104,7 @@ function serveSkillFile(res, url, services, token) {
|
|
|
1168
1104
|
jsonRes(res, 400, { error: "Invalid skill directory" });
|
|
1169
1105
|
return;
|
|
1170
1106
|
}
|
|
1171
|
-
const skillsRoot = source === "global" ? workspace.skillsDir :
|
|
1107
|
+
const skillsRoot = source === "global" ? workspace.skillsDir : office.skillsDir;
|
|
1172
1108
|
const safe = safeJoinUnderRoot(skillsRoot, join(directory, "SKILL.md"));
|
|
1173
1109
|
if (safe.error) {
|
|
1174
1110
|
jsonRes(res, 400, { error: safe.error });
|
|
@@ -1177,12 +1113,12 @@ function serveSkillFile(res, url, services, token) {
|
|
|
1177
1113
|
servePreviewFile(res, safe.absolute, { source, directory }, "Skill file not found");
|
|
1178
1114
|
}
|
|
1179
1115
|
const SKILL_DIRECTORY_PATTERN = /^[a-z0-9-]+$/;
|
|
1180
|
-
function resolveSkillsRoot(
|
|
1116
|
+
function resolveSkillsRoot(office, source) {
|
|
1181
1117
|
if (source !== "global" && source !== "conversation") {
|
|
1182
1118
|
return { error: "Invalid skill source" };
|
|
1183
1119
|
}
|
|
1184
1120
|
return {
|
|
1185
|
-
root: source === "global" ? workspace.skillsDir :
|
|
1121
|
+
root: source === "global" ? office.workspace.skillsDir : office.skillsDir,
|
|
1186
1122
|
source,
|
|
1187
1123
|
};
|
|
1188
1124
|
}
|
|
@@ -1192,15 +1128,10 @@ async function serveSkillMutation(res, body, services, token) {
|
|
|
1192
1128
|
jsonRes(res, 400, { error: "action must be 'save' or 'delete'" });
|
|
1193
1129
|
return;
|
|
1194
1130
|
}
|
|
1195
|
-
const
|
|
1196
|
-
if (
|
|
1197
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1201
|
-
if (!workspace)
|
|
1131
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
1132
|
+
if (!office)
|
|
1202
1133
|
return;
|
|
1203
|
-
const resolved = resolveSkillsRoot(
|
|
1134
|
+
const resolved = resolveSkillsRoot(office, body.source);
|
|
1204
1135
|
if ("error" in resolved) {
|
|
1205
1136
|
jsonRes(res, 400, { error: resolved.error });
|
|
1206
1137
|
return;
|
|
@@ -1286,9 +1217,7 @@ function requireAdminEventStore(res, services, office) {
|
|
|
1286
1217
|
return services.eventStore(office);
|
|
1287
1218
|
}
|
|
1288
1219
|
async function serveEventsList(res, services) {
|
|
1289
|
-
const workspace =
|
|
1290
|
-
if (!workspace)
|
|
1291
|
-
return;
|
|
1220
|
+
const { workspace } = services;
|
|
1292
1221
|
if (!services.eventStore) {
|
|
1293
1222
|
jsonRes(res, 503, { error: "Working directory not available" });
|
|
1294
1223
|
return;
|
|
@@ -1300,28 +1229,21 @@ async function serveEventsList(res, services) {
|
|
|
1300
1229
|
jsonRes(res, 200, { events });
|
|
1301
1230
|
}
|
|
1302
1231
|
async function serveConversationEventsList(res, url, services, token) {
|
|
1303
|
-
const
|
|
1304
|
-
if (
|
|
1305
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1232
|
+
const office = scopedOffice(res, services, resolveConversationFromQuery(url, token));
|
|
1233
|
+
if (!office)
|
|
1306
1234
|
return;
|
|
1307
|
-
|
|
1308
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1309
|
-
if (!workspace)
|
|
1310
|
-
return;
|
|
1311
|
-
const store = requireAdminEventStore(res, services, workspace.office(scope.address));
|
|
1235
|
+
const store = requireAdminEventStore(res, services, office);
|
|
1312
1236
|
if (!store)
|
|
1313
1237
|
return;
|
|
1314
1238
|
jsonRes(res, 200, {
|
|
1315
|
-
conversationId:
|
|
1239
|
+
conversationId: office.address.conversationId,
|
|
1316
1240
|
events: await listOfficeEvents(store),
|
|
1317
1241
|
});
|
|
1318
1242
|
}
|
|
1319
1243
|
async function serveConversationEventDelete(res, body, services, token) {
|
|
1320
|
-
const
|
|
1321
|
-
if (
|
|
1322
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1244
|
+
const office = scopedOffice(res, services, resolveTargetConversation(body, token));
|
|
1245
|
+
if (!office)
|
|
1323
1246
|
return;
|
|
1324
|
-
}
|
|
1325
1247
|
let name;
|
|
1326
1248
|
try {
|
|
1327
1249
|
name = validateEventFilename(typeof body.name === "string" ? body.name : "");
|
|
@@ -1330,10 +1252,7 @@ async function serveConversationEventDelete(res, body, services, token) {
|
|
|
1330
1252
|
jsonRes(res, 400, { error: "Invalid name" });
|
|
1331
1253
|
return;
|
|
1332
1254
|
}
|
|
1333
|
-
const
|
|
1334
|
-
if (!workspace)
|
|
1335
|
-
return;
|
|
1336
|
-
const store = requireAdminEventStore(res, services, workspace.office(scope.address));
|
|
1255
|
+
const store = requireAdminEventStore(res, services, office);
|
|
1337
1256
|
if (!store)
|
|
1338
1257
|
return;
|
|
1339
1258
|
try {
|