pilotswarm 0.5.28 → 0.5.30
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/mcp/dist/bin/pilotswarm-mcp.js +4 -0
- package/mcp/dist/bin/pilotswarm-mcp.js.map +1 -1
- package/mcp/dist/src/context.d.ts +43 -10
- package/mcp/dist/src/context.d.ts.map +1 -1
- package/mcp/dist/src/context.js +15 -7
- package/mcp/dist/src/context.js.map +1 -1
- package/mcp/dist/src/resources/artifacts.d.ts.map +1 -1
- package/mcp/dist/src/resources/artifacts.js +4 -3
- package/mcp/dist/src/resources/artifacts.js.map +1 -1
- package/mcp/dist/src/server.d.ts.map +1 -1
- package/mcp/dist/src/server.js +2 -0
- package/mcp/dist/src/server.js.map +1 -1
- package/mcp/dist/src/tools/agent-packages.d.ts +14 -0
- package/mcp/dist/src/tools/agent-packages.d.ts.map +1 -0
- package/mcp/dist/src/tools/agent-packages.js +126 -0
- package/mcp/dist/src/tools/agent-packages.js.map +1 -0
- package/mcp/dist/src/tools/agents.d.ts.map +1 -1
- package/mcp/dist/src/tools/agents.js +18 -0
- package/mcp/dist/src/tools/agents.js.map +1 -1
- package/mcp/dist/src/tools/artifacts.d.ts.map +1 -1
- package/mcp/dist/src/tools/artifacts.js +12 -11
- package/mcp/dist/src/tools/artifacts.js.map +1 -1
- package/mcp/dist/src/tools/capabilities.js +1 -1
- package/mcp/dist/src/tools/capabilities.js.map +1 -1
- package/mcp/dist/src/tools/observability.js +1 -1
- package/mcp/dist/src/tools/observability.js.map +1 -1
- package/mcp/dist/src/tools/sessions.js +2 -2
- package/mcp/dist/src/tools/sessions.js.map +1 -1
- package/mcp/dist/src/tools/system.d.ts.map +1 -1
- package/mcp/dist/src/tools/system.js +56 -5
- package/mcp/dist/src/tools/system.js.map +1 -1
- package/mcp/dist/src/tools/turn-control.js +1 -1
- package/mcp/dist/src/tools/turn-control.js.map +1 -1
- package/mcp/dist/src/util/command.d.ts +3 -3
- package/mcp/dist/src/util/command.d.ts.map +1 -1
- package/mcp/dist/src/util/command.js +1 -1
- package/mcp/dist/src/util/command.js.map +1 -1
- package/package.json +6 -6
- package/tui/bin/tui.js +6 -0
- package/tui/src/agents-cli.js +442 -0
- package/tui/src/app.js +44 -1
- package/tui/src/node-sdk-transport.js +386 -5
- package/tui/src/plugin-config.js +17 -0
- package/ui/core/src/commands.js +7 -0
- package/ui/core/src/controller.js +498 -33
- package/ui/core/src/formatting.js +60 -16
- package/ui/core/src/index.js +2 -0
- package/ui/core/src/reducer.js +421 -34
- package/ui/core/src/repo-import.js +298 -0
- package/ui/core/src/repo-links.js +54 -0
- package/ui/core/src/selectors.js +879 -91
- package/ui/core/src/session-tree.js +109 -8
- package/ui/core/src/state.js +108 -2
- package/ui/core/src/themes/helpers.js +4 -1
- package/ui/core/src/themes/index.js +4 -10
- package/ui/core/src/themes/workspace-dark-rich.js +65 -0
- package/ui/core/src/themes/workspace-dark.js +65 -0
- package/ui/react/src/components.js +181 -4
- package/ui/react/src/web-app.js +1410 -91
- package/web/api/router.js +20 -2
- package/web/authz.js +4 -10
- package/web/dist/assets/{abnfDiagram-VRR7QNED-DLV45wH4.js → abnfDiagram-VRR7QNED-Cimc0f-u.js} +1 -1
- package/web/dist/assets/{arc-CE4xAYdE.js → arc-D8-_Dx5J.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DfzR8-9F.js → architectureDiagram-ZJ3FMSHR-lzMAuQHJ.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-C2pZdqvH.js → blockDiagram-677ZJIJ3-Dp2CWjVS.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-CKRspTan.js → c4Diagram-LMCZKHZV-DF93UkOy.js} +1 -1
- package/web/dist/assets/channel-OwWeiCox.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-CNjLrfHT.js → chunk-2Q5K7J3B-pbsQRM-F.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-Bv5EwyaJ.js → chunk-32BRIVSS-B_f-gG1O.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-NDBesEbJ.js → chunk-5VM5RSS4-gqlpJDmt.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-DHQht9ty.js → chunk-EX3LRPZG-Cms7hPmM.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-r0OQpJRF.js → chunk-JWPE2WC7-Boq5PtPI.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-fcwIDgtl.js → chunk-MOJQB5TN-DVn9i8zt.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-x7XQlDO1.js → chunk-RYQCIY6F-a1vKs3UX.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-U8ZNVy1m.js → chunk-V7JOEXUC-i60p5fHd.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-DLEdPLcN.js → chunk-VR4S4FIN-a8p0o-8B.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-TeyAtB-i.js → chunk-XXDRQBXY-BwP1yBKd.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-CDaP7nuC.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-CDaP7nuC.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-B3bAl1-Z.js → cose-bilkent-JH36ORCC-HW6kGgze.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-DexP8nG-.js → cynefin-VYW2F7L2-YUFMn-vE.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-Doa2XnSs.js → cynefinDiagram-TSTJHNR4-Brdy17i4.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-DZMBKQcJ.js → dagre-VKFMJZFB-CAiSJJj-.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-BkzibmSq.js → diagram-FQU43EPY-B63Cqe0J.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-CihyHR3a.js → diagram-G47NLZAW-ByktrDJ2.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-OMDUUxwW.js → diagram-NH7WQ7WH-BLSfRMoE.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-Lc0imJba.js → diagram-OA4YK3LP-DNVu-zpk.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-Dlcy8hPe.js → diagram-WEI45ONY-CNf4Sz-I.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-Dh3u17Dg.js → ebnfDiagram-CCIWWBDH-9CT3ergx.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-C0OhlUC2.js → erDiagram-Q63AITRT-BPJHAMgl.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-CADKxT-x.js → flowDiagram-23GEKE2U-BVtpr6Iu.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-iVVdxWB7.js → ganttDiagram-NO4QXBWP-BTHnv9sV.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-BwDUec_a.js → gitGraphDiagram-IHSO6WYX-BfmtqbL5.js} +1 -1
- package/web/dist/assets/index-B37T1c2s.css +1 -0
- package/web/dist/assets/index-Bmb8qK39.js +24 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-CGPUif0s.js → infoDiagram-FWYZ7A6U-kU4i4HRu.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-QYnrl-KA.js → ishikawaDiagram-FXEZZL3T-Ch7i-E8O.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-CeKixxL0.js → journeyDiagram-5HDEW3XC-CPQxp0ps.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-CeYokFBS.js → kanban-definition-HUTT4EX6-BBgMVqoJ.js} +1 -1
- package/web/dist/assets/{linear-BbA6_gnz.js → linear-rwksvzXJ.js} +1 -1
- package/web/dist/assets/{mermaid.core--xGRoQTa.js → mermaid.core-o7PszRUc.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-D53u8h0z.js → mindmap-definition-LN4V7U3C-wgOTdzlq.js} +1 -1
- package/web/dist/assets/{papaparse.min-BU0o7jXn.js → papaparse.min-BRXtjEI6.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-B7QSyG-3.js → pegDiagram-2B236MQR-BkhQqqTm.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-XtSGL7GG.js → pieDiagram-ENE6RG2P-CcMnvxUz.js} +1 -1
- package/web/dist/assets/pilotswarm-DYXtzuUN.js +99 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-LZoYSbTH.js → quadrantDiagram-ABIIQ3AL-_KD1QeEr.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-B5FREx2U.js → railroadDiagram-RFXS5EU6-CqI0olIe.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-1VRFkBQX.js → requirementDiagram-TGXJPOKE-1JxRwusF.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-J456gWvu.js → sankeyDiagram-HTMAVEWB-9IpFoKzi.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CaCcw6KO.js → sequenceDiagram-DBY2YBRQ-teyObSQ9.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DClsP3_O.js → sizeCapture-X5ZJPWSS-DVhU2J_6.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-D6VyrKhd.js → stateDiagram-2N3HPSRC-DHPoWpIQ.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DJjZWYLS.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-q678iAuo.js → swimlanes-5IMT3BWC-BxTtDQoB.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CV12vDcK.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-WnPK_zhe.js → timeline-definition-FHXFAJF6-D0wXZx-4.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-WpumuLld.js → vennDiagram-L72KCM5P-BSFyI3iV.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-CBrYLUm_.js → wardleyDiagram-EHGQE667-BN-S7hh0.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-DbX1rnDW.js → xychartDiagram-FW5EYKEG-nDNuPs7o.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/runtime.js +36 -4
- package/ui/core/src/themes/daylight.js +0 -62
- package/ui/core/src/themes/light-high-contrast.js +0 -65
- package/ui/core/src/themes/paper-ink.js +0 -62
- package/ui/core/src/themes/solarized-ops.js +0 -59
- package/ui/core/src/themes/tokyo-night.js +0 -56
- package/web/dist/assets/channel-D7p-E4El.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-yFZ-1yMb.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-yFZ-1yMb.js +0 -1
- package/web/dist/assets/index-BGWTu1KK.css +0 -1
- package/web/dist/assets/index-Djfdr7ts.js +0 -24
- package/web/dist/assets/pilotswarm-CtO819QH.js +0 -94
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-D7eKFhHb.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-DlVHQBQx.js +0 -8
|
@@ -17,6 +17,14 @@ import {
|
|
|
17
17
|
ATTACHMENT_MAX_BYTES,
|
|
18
18
|
ATTACHMENTS_MAX_COUNT,
|
|
19
19
|
ATTACHMENTS_MAX_TOTAL_BYTES,
|
|
20
|
+
PgSessionCatalog,
|
|
21
|
+
publishAgentPackageDir,
|
|
22
|
+
deleteAgentPackageEverywhere,
|
|
23
|
+
fetchAgentPackageTarGz,
|
|
24
|
+
readAgentPackageTarGz,
|
|
25
|
+
listBundledAgentNames,
|
|
26
|
+
normalizeAgentName,
|
|
27
|
+
AgentPackageValidationError,
|
|
20
28
|
} from "pilotswarm-sdk";
|
|
21
29
|
import { startEmbeddedWorkers, stopEmbeddedWorkers } from "./embedded-workers.js";
|
|
22
30
|
import { getPluginDirsFromEnv } from "./plugin-config.js";
|
|
@@ -604,7 +612,10 @@ export class NodeSdkTransport {
|
|
|
604
612
|
this.client ? this.client.stop() : Promise.resolve(),
|
|
605
613
|
this.mgmt.stop(),
|
|
606
614
|
stopEmbeddedWorkers(this.workers),
|
|
615
|
+
this._agentPkgCatalog ? this._agentPkgCatalog.close() : Promise.resolve(),
|
|
607
616
|
]);
|
|
617
|
+
this._agentPkgCatalog = null;
|
|
618
|
+
this._agentPkgCatalogPromise = null;
|
|
608
619
|
this.client = null;
|
|
609
620
|
}
|
|
610
621
|
|
|
@@ -623,6 +634,374 @@ export class NodeSdkTransport {
|
|
|
623
634
|
return loadSessionCreationMetadataFromPluginDirs(getPluginDirsFromEnv());
|
|
624
635
|
}
|
|
625
636
|
|
|
637
|
+
async listCreatableAgents(viewer = null, isAdmin = true) {
|
|
638
|
+
const baked = this.creatableAgents.map((agent) => ({ ...agent, source: "builtin" }));
|
|
639
|
+
const registry = await this._listRegistryCreatableAgents(viewer, isAdmin);
|
|
640
|
+
// Baked agents are deployment code — they win a (normalized) name
|
|
641
|
+
// collision; the colliding registry entry is dropped, matching the
|
|
642
|
+
// validator that refuses such packages going forward.
|
|
643
|
+
const taken = new Set(baked.map((agent) => normalizeAgentName(agent.name)));
|
|
644
|
+
const merged = [...baked];
|
|
645
|
+
for (const entry of registry) {
|
|
646
|
+
if (taken.has(normalizeAgentName(entry.name))) {
|
|
647
|
+
// Baked wins by design — but never silently: the package's
|
|
648
|
+
// agent is unreachable until it renames.
|
|
649
|
+
console.warn(`[transport] package agent "${entry.name}" (${entry.packageName}) collides with a baked agent and is hidden from the catalog`);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
taken.add(normalizeAgentName(entry.name));
|
|
653
|
+
merged.push(entry);
|
|
654
|
+
}
|
|
655
|
+
return merged;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Registry package agents as creatable-agent entries, viewer-scoped. */
|
|
659
|
+
async _listRegistryCreatableAgents(viewer, isAdmin) {
|
|
660
|
+
const ctx = await this._agentPackagesContext();
|
|
661
|
+
if (!ctx) return [];
|
|
662
|
+
try {
|
|
663
|
+
const packages = await ctx.catalog.listAgentPackages(
|
|
664
|
+
viewer ? { provider: viewer.provider, subject: viewer.subject } : null,
|
|
665
|
+
Boolean(isAdmin),
|
|
666
|
+
);
|
|
667
|
+
// Ownership is decided HERE, where the viewer's identity is known.
|
|
668
|
+
// An ADMIN sees every user's user-scoped packages, so "scope is
|
|
669
|
+
// user" does not mean "mine" — grouping on scope alone filed other
|
|
670
|
+
// people's agents onto the caller's shelf. Sending a boolean rather
|
|
671
|
+
// than the owner's identifiers keeps the catalog from telling every
|
|
672
|
+
// caller who installed what. No viewer at all (single-user or
|
|
673
|
+
// auth-disabled) means any user-scoped package is the caller's.
|
|
674
|
+
const principalKey = (principal) => (principal?.provider && principal?.subject
|
|
675
|
+
? `${principal.provider}:${principal.subject}`.toLowerCase()
|
|
676
|
+
: null);
|
|
677
|
+
const viewerKey = principalKey(viewer);
|
|
678
|
+
const entries = [];
|
|
679
|
+
for (const pkg of packages) {
|
|
680
|
+
if (!pkg.enabled || !pkg.active) continue;
|
|
681
|
+
const ownerKey = principalKey(pkg.owner);
|
|
682
|
+
const mine = pkg.scope === "user" && (viewerKey === null || ownerKey === viewerKey);
|
|
683
|
+
const agents = Array.isArray(pkg.active.manifest?.agents) ? pkg.active.manifest.agents : [];
|
|
684
|
+
for (const agent of agents) {
|
|
685
|
+
if (!agent?.name) continue;
|
|
686
|
+
entries.push({
|
|
687
|
+
name: agent.name,
|
|
688
|
+
title: agent.title || undefined,
|
|
689
|
+
description: agent.description || undefined,
|
|
690
|
+
tools: Array.isArray(agent.tools) ? agent.tools : undefined,
|
|
691
|
+
// Carried so a PACKAGE agent presents itself like a
|
|
692
|
+
// baked one: the picker sends these with the create
|
|
693
|
+
// call, so without them a package agent could neither
|
|
694
|
+
// self-start (initialPrompt) nor show its own splash.
|
|
695
|
+
splash: typeof agent.splash === "string" && agent.splash.trim() ? agent.splash : undefined,
|
|
696
|
+
splashMobile: typeof agent.splashMobile === "string" && agent.splashMobile.trim() ? agent.splashMobile : undefined,
|
|
697
|
+
initialPrompt: typeof agent.initialPrompt === "string" && agent.initialPrompt.trim() ? agent.initialPrompt : undefined,
|
|
698
|
+
source: "package",
|
|
699
|
+
packageName: pkg.name,
|
|
700
|
+
packageSemver: pkg.active.semver,
|
|
701
|
+
scope: pkg.scope,
|
|
702
|
+
mine,
|
|
703
|
+
ownerLabel: pkg.createdBy || pkg.owner?.subject || null,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return entries;
|
|
708
|
+
} catch (err) {
|
|
709
|
+
console.warn(`[transport] agent-package catalog unavailable: ${err?.message || err}`);
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Enforce user-scope visibility at create time and admit package agents
|
|
716
|
+
* into the client's live allowlist. Baked agents pass through untouched.
|
|
717
|
+
*/
|
|
718
|
+
async _authorizePackageAgentCreate(agentName, owner, isAdmin) {
|
|
719
|
+
const normalized = normalizeAgentName(agentName);
|
|
720
|
+
const baked = this.creatableAgents.find((agent) => normalizeAgentName(agent.name) === normalized);
|
|
721
|
+
if (baked) return baked.name;
|
|
722
|
+
const registry = await this._listRegistryCreatableAgents(owner, isAdmin);
|
|
723
|
+
const match = registry.find((entry) => normalizeAgentName(entry.name) === normalized);
|
|
724
|
+
if (!match) {
|
|
725
|
+
// Either the agent doesn't exist or it is a user-scope package the
|
|
726
|
+
// caller can't see — same answer either way (no existence oracle).
|
|
727
|
+
const err = new Error(`agent "${agentName}" is not available to you`);
|
|
728
|
+
err.code = "FORBIDDEN";
|
|
729
|
+
throw err;
|
|
730
|
+
}
|
|
731
|
+
// Client holds this.allowedAgentNames BY REFERENCE (live getter) —
|
|
732
|
+
// pushing here makes the delegated create pass client validation.
|
|
733
|
+
if (this.allowedAgentNames.length > 0 && !this.allowedAgentNames.includes(match.name)) {
|
|
734
|
+
this.allowedAgentNames.push(match.name);
|
|
735
|
+
}
|
|
736
|
+
return match.name;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Lazy direct-store registry context; null in pure web/client setups AND
|
|
741
|
+
* on init failure. A failed init is NEVER cached — the portal boots
|
|
742
|
+
* alongside 8 workers all polling the migration advisory lock, and one
|
|
743
|
+
* transient contention loss must not permanently reduce the catalog to
|
|
744
|
+
* baked agents (observed live: a cached rejection made every later
|
|
745
|
+
* listCreatableAgents union silently registry-less until pod restart).
|
|
746
|
+
*/
|
|
747
|
+
async _agentPackagesContext() {
|
|
748
|
+
if (!this.store || !this.artifactStore) return null;
|
|
749
|
+
if (this._agentPkgCatalog) {
|
|
750
|
+
return { catalog: this._agentPkgCatalog, artifactStore: this.artifactStore };
|
|
751
|
+
}
|
|
752
|
+
try {
|
|
753
|
+
this._agentPkgCatalogPromise ??= (async () => {
|
|
754
|
+
const catalog = await PgSessionCatalog.create(this.store, process.env.PILOTSWARM_CMS_SCHEMA || undefined, {
|
|
755
|
+
...(this.useManagedIdentity !== undefined ? { useManagedIdentity: this.useManagedIdentity } : {}),
|
|
756
|
+
...(this.aadDbUser ? { aadUser: this.aadDbUser } : {}),
|
|
757
|
+
});
|
|
758
|
+
await catalog.initialize();
|
|
759
|
+
this._agentPkgCatalog = catalog;
|
|
760
|
+
return catalog;
|
|
761
|
+
})();
|
|
762
|
+
await this._agentPkgCatalogPromise;
|
|
763
|
+
} catch (err) {
|
|
764
|
+
this._agentPkgCatalogPromise = null;
|
|
765
|
+
console.warn(`[transport] agent-package registry init failed (will retry on next call): ${err?.message || err}`);
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
return { catalog: this._agentPkgCatalog, artifactStore: this.artifactStore };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
_requirePrincipal(owner, isAdmin) {
|
|
772
|
+
if (owner) return { provider: owner.provider, subject: owner.subject };
|
|
773
|
+
if (isAdmin) return null; // anonymous deployments: admin-equivalent
|
|
774
|
+
const err = new Error("agent-package operations require an authenticated principal");
|
|
775
|
+
err.code = "FORBIDDEN";
|
|
776
|
+
throw err;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
_createdByLabel(owner) {
|
|
780
|
+
return owner?.email || owner?.displayName || (owner ? `${owner.provider}:${owner.subject}` : "system");
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* The registry raises typed failures as MESSAGE PREFIXES
|
|
785
|
+
* (AGENT_PACKAGE_FORBIDDEN: …). Attach the matching error.code so the
|
|
786
|
+
* router maps them to 403/404/409 instead of a generic 500 — without
|
|
787
|
+
* this, "you don't own this package" reaches the browser as
|
|
788
|
+
* "Internal server error".
|
|
789
|
+
*/
|
|
790
|
+
async _mapAgentPackageErrors(run) {
|
|
791
|
+
try {
|
|
792
|
+
return await run();
|
|
793
|
+
} catch (error) {
|
|
794
|
+
const message = String(error?.message || "");
|
|
795
|
+
if (!error?.code) {
|
|
796
|
+
if (/AGENT_(?:PACKAGE|SOURCE)_[A-Z_]*FORBIDDEN/.test(message)) error.code = "FORBIDDEN";
|
|
797
|
+
else if (/AGENT_(?:PACKAGE|SOURCE)_[A-Z_]*NOT_FOUND/.test(message)) error.code = "NOT_FOUND";
|
|
798
|
+
else if (/AGENT_PACKAGE_(?:SEMVER_CONFLICT|SCOPE_MISMATCH)/.test(message)) error.code = "CONFLICT";
|
|
799
|
+
else if (/AGENT_PACKAGE_BAD_SCOPE/.test(message)) error.code = "INVALID_REQUEST";
|
|
800
|
+
}
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ── Agent packages: registry CRUD (portal ops surface) ──────
|
|
806
|
+
|
|
807
|
+
async listAgentPackages(owner, isAdmin) {
|
|
808
|
+
const ctx = await this._agentPackagesContext();
|
|
809
|
+
if (!ctx) return [];
|
|
810
|
+
return ctx.catalog.listAgentPackages(this._requirePrincipal(owner, isAdmin), Boolean(isAdmin));
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async getAgentPackage(name, owner, isAdmin) {
|
|
814
|
+
const ctx = await this._agentPackagesContext();
|
|
815
|
+
if (!ctx) return null;
|
|
816
|
+
return ctx.catalog.getAgentPackage(name, this._requirePrincipal(owner, isAdmin), Boolean(isAdmin));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
async listAgentWorkerState() {
|
|
824
|
+
const ctx = await this._agentPackagesContext();
|
|
825
|
+
if (!ctx) return [];
|
|
826
|
+
return ctx.catalog.listAgentWorkerState();
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async listWorkers() {
|
|
830
|
+
const ctx = await this._agentPackagesContext();
|
|
831
|
+
if (!ctx) return [];
|
|
832
|
+
return ctx.catalog.listWorkers();
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async setAgentPackageScope(name, scope, owner, isAdmin) {
|
|
836
|
+
const ctx = await this._agentPackagesContext();
|
|
837
|
+
if (!ctx) throw new Error("agent packages are not available on this deployment");
|
|
838
|
+
await this._mapAgentPackageErrors(() => ctx.catalog.setAgentPackageScope(name, scope === "shared" ? "shared" : "user",
|
|
839
|
+
this._requirePrincipal(owner, isAdmin), Boolean(isAdmin)));
|
|
840
|
+
return { ok: true };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
async setAgentPackageEnabled(name, enabled, owner, isAdmin) {
|
|
844
|
+
const ctx = await this._agentPackagesContext();
|
|
845
|
+
if (!ctx) throw new Error("agent packages are not available on this deployment");
|
|
846
|
+
await this._mapAgentPackageErrors(() => ctx.catalog.setAgentPackageEnabled(name, Boolean(enabled),
|
|
847
|
+
this._requirePrincipal(owner, isAdmin), Boolean(isAdmin)));
|
|
848
|
+
return { ok: true };
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
async pinAgentPackageVersion(name, semver, owner, isAdmin) {
|
|
852
|
+
const ctx = await this._agentPackagesContext();
|
|
853
|
+
if (!ctx) throw new Error("agent packages are not available on this deployment");
|
|
854
|
+
await this._mapAgentPackageErrors(() => ctx.catalog.pinAgentPackageVersion(name, String(semver || ""),
|
|
855
|
+
this._requirePrincipal(owner, isAdmin), Boolean(isAdmin)));
|
|
856
|
+
return { ok: true };
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async deleteAgentPackage(name, owner, isAdmin) {
|
|
860
|
+
const ctx = await this._agentPackagesContext();
|
|
861
|
+
if (!ctx) throw new Error("agent packages are not available on this deployment");
|
|
862
|
+
await this._mapAgentPackageErrors(() => deleteAgentPackageEverywhere(ctx, name, this._requirePrincipal(owner, isAdmin), Boolean(isAdmin)));
|
|
863
|
+
this._agentPkgTarCache?.clear?.();
|
|
864
|
+
return { ok: true };
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/** Inline-files upload → the one publish pipeline. ≤ 2 MB decoded. */
|
|
868
|
+
async uploadAgentPackage(files, scope, owner, isAdmin) {
|
|
869
|
+
const ctx = await this._agentPackagesContext();
|
|
870
|
+
if (!ctx) throw new Error("agent packages are not available on this deployment");
|
|
871
|
+
const principal = this._requirePrincipal(owner, isAdmin);
|
|
872
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
873
|
+
throw new Error("upload requires files: [{ path, contentBase64 }]");
|
|
874
|
+
}
|
|
875
|
+
if (files.length > 2000) throw new Error("upload exceeds 2000 files");
|
|
876
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "ps-agent-upload-"));
|
|
877
|
+
try {
|
|
878
|
+
let total = 0;
|
|
879
|
+
for (const file of files) {
|
|
880
|
+
const rel = String(file?.path || "");
|
|
881
|
+
if (!rel || rel.startsWith("/") || rel.includes("\\") || rel.split("/").some((p) => p === "..")) {
|
|
882
|
+
throw new Error(`upload file path is not a safe relative path: ${rel}`);
|
|
883
|
+
}
|
|
884
|
+
const bytes = Buffer.from(String(file?.contentBase64 || ""), "base64");
|
|
885
|
+
total += bytes.length;
|
|
886
|
+
if (total > 2 * 1024 * 1024) throw new Error("upload exceeds the 2 MB envelope — use a repo/url source or the CLI for larger packages");
|
|
887
|
+
const target = path.resolve(tmp, rel);
|
|
888
|
+
if (target !== tmp && !target.startsWith(tmp + path.sep)) {
|
|
889
|
+
throw new Error(`upload file path escapes the package root: ${rel}`);
|
|
890
|
+
}
|
|
891
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
892
|
+
fs.writeFileSync(target, bytes);
|
|
893
|
+
}
|
|
894
|
+
const outcome = await this._mapAgentPackageErrors(() => publishAgentPackageDir(ctx, {
|
|
895
|
+
dir: tmp,
|
|
896
|
+
scope: scope === "shared" ? "shared" : "user",
|
|
897
|
+
owner: principal,
|
|
898
|
+
createdBy: this._createdByLabel(owner),
|
|
899
|
+
isAdmin: Boolean(isAdmin),
|
|
900
|
+
validate: { reservedAgentNames: this._reservedAgentNames() },
|
|
901
|
+
}));
|
|
902
|
+
const { manifest: _m, ...summary } = outcome;
|
|
903
|
+
return summary;
|
|
904
|
+
} catch (err) {
|
|
905
|
+
if (err instanceof AgentPackageValidationError) {
|
|
906
|
+
const wrapped = new Error(err.message);
|
|
907
|
+
wrapped.code = "VALIDATION_FAILED";
|
|
908
|
+
wrapped.validation = err.validation;
|
|
909
|
+
throw wrapped;
|
|
910
|
+
}
|
|
911
|
+
throw err;
|
|
912
|
+
} finally {
|
|
913
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
_reservedAgentNames() {
|
|
918
|
+
if (!this._reservedNamesCache) {
|
|
919
|
+
const bundled = (() => { try { return listBundledAgentNames(); } catch { return []; } })();
|
|
920
|
+
if (bundled.length === 0) {
|
|
921
|
+
// System agent names (sweeper, resourcemgr, …) live ONLY here —
|
|
922
|
+
// an empty list means packages could shadow them silently.
|
|
923
|
+
console.warn("[transport] listBundledAgentNames returned nothing — bundled plugins/ missing from the SDK install? Reserved-name validation is degraded.");
|
|
924
|
+
}
|
|
925
|
+
this._reservedNamesCache = [
|
|
926
|
+
...bundled,
|
|
927
|
+
...this.creatableAgents.map((agent) => agent.name),
|
|
928
|
+
];
|
|
929
|
+
}
|
|
930
|
+
return this._reservedNamesCache;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// ── Agent packages: workspace viewer (tree + file preview) ──
|
|
934
|
+
|
|
935
|
+
async _agentPackageEntries(name, semver, owner, isAdmin) {
|
|
936
|
+
const ctx = await this._agentPackagesContext();
|
|
937
|
+
if (!ctx) throw new Error("agent packages are not available on this deployment");
|
|
938
|
+
const detail = await ctx.catalog.getAgentPackage(name, this._requirePrincipal(owner, isAdmin), Boolean(isAdmin));
|
|
939
|
+
if (!detail) throw Object.assign(new Error(`package "${name}" not found`), { code: "NOT_FOUND" });
|
|
940
|
+
const version = semver
|
|
941
|
+
? detail.versions.find((v) => v.semver === semver)
|
|
942
|
+
: detail.versions.find((v) => v.versionId === detail.activeVersionId) ?? detail.versions[0];
|
|
943
|
+
if (!version) throw Object.assign(new Error(`package "${name}" has no ${semver ? `version ${semver}` : "versions"}`), { code: "NOT_FOUND" });
|
|
944
|
+
const cacheKey = `${name}@${version.semver}.${version.sha256.slice(0, 12)}`;
|
|
945
|
+
this._agentPkgTarCache ??= new Map();
|
|
946
|
+
let entries = this._agentPkgTarCache.get(cacheKey);
|
|
947
|
+
if (!entries) {
|
|
948
|
+
const targz = await this._mapAgentPackageErrors(
|
|
949
|
+
() => fetchAgentPackageTarGz(ctx.artifactStore, version.artifactFilename, version.sha256),
|
|
950
|
+
);
|
|
951
|
+
entries = readAgentPackageTarGz(targz);
|
|
952
|
+
this._agentPkgTarCache.set(cacheKey, entries);
|
|
953
|
+
// Bound by BYTES (the real resource), not just entry count.
|
|
954
|
+
const bytesOf = (list) => list.reduce((sum, e) => sum + e.body.length, 0);
|
|
955
|
+
let totalBytes = 0;
|
|
956
|
+
for (const cached of this._agentPkgTarCache.values()) totalBytes += bytesOf(cached);
|
|
957
|
+
while ((totalBytes > 64 * 1024 * 1024 || this._agentPkgTarCache.size > 6)
|
|
958
|
+
&& this._agentPkgTarCache.size > 1) {
|
|
959
|
+
const oldest = this._agentPkgTarCache.keys().next().value;
|
|
960
|
+
totalBytes -= bytesOf(this._agentPkgTarCache.get(oldest));
|
|
961
|
+
this._agentPkgTarCache.delete(oldest);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return { entries, version };
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async getAgentPackageTree(name, semver, owner, isAdmin) {
|
|
968
|
+
const { entries, version } = await this._agentPackageEntries(name, semver, owner, isAdmin);
|
|
969
|
+
const dirs = new Set();
|
|
970
|
+
const files = [];
|
|
971
|
+
for (const entry of entries) {
|
|
972
|
+
files.push({ path: entry.name, size: entry.body.length });
|
|
973
|
+
const parts = entry.name.split("/");
|
|
974
|
+
for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join("/"));
|
|
975
|
+
}
|
|
976
|
+
files.sort((a, b) => (a.path < b.path ? -1 : 1));
|
|
977
|
+
return {
|
|
978
|
+
name,
|
|
979
|
+
semver: version.semver,
|
|
980
|
+
sha256: version.sha256,
|
|
981
|
+
dirs: [...dirs].sort(),
|
|
982
|
+
files,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async getAgentPackageFile(name, semver, filePath, owner, isAdmin) {
|
|
987
|
+
const { entries, version } = await this._agentPackageEntries(name, semver, owner, isAdmin);
|
|
988
|
+
const rel = String(filePath || "");
|
|
989
|
+
const entry = entries.find((candidate) => candidate.name === rel);
|
|
990
|
+
if (!entry) throw Object.assign(new Error(`"${rel}" is not in ${name}@${version.semver}`), { code: "NOT_FOUND" });
|
|
991
|
+
const MAX_PREVIEW = 256 * 1024;
|
|
992
|
+
const body = entry.body.subarray(0, MAX_PREVIEW);
|
|
993
|
+
const isBinary = body.includes(0);
|
|
994
|
+
return {
|
|
995
|
+
name,
|
|
996
|
+
semver: version.semver,
|
|
997
|
+
path: rel,
|
|
998
|
+
size: entry.body.length,
|
|
999
|
+
truncated: entry.body.length > MAX_PREVIEW,
|
|
1000
|
+
encoding: isBinary ? "base64" : "utf8",
|
|
1001
|
+
content: isBinary ? body.toString("base64") : body.toString("utf8"),
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
626
1005
|
getWorkerCount() {
|
|
627
1006
|
return this.workers.length || (this.mode === "remote" ? 0 : parseInt(process.env.WORKERS || "4", 10));
|
|
628
1007
|
}
|
|
@@ -1080,9 +1459,14 @@ export class NodeSdkTransport {
|
|
|
1080
1459
|
return { sessionId: session.sessionId, model: effectiveModel, reasoningEffort: reasoningEffort || undefined, contextTier: contextTier || undefined };
|
|
1081
1460
|
}
|
|
1082
1461
|
|
|
1083
|
-
async createSessionForAgent(agentName, { model, reasoningEffort, contextTier, title, splash, splashMobile, initialPrompt, owner, groupId, visibility } = {}) {
|
|
1462
|
+
async createSessionForAgent(agentName, { model, reasoningEffort, contextTier, title, splash, splashMobile, initialPrompt, owner, isAdmin, groupId, visibility } = {}) {
|
|
1463
|
+
// Registry (package) agents are not in the static baked allowlist —
|
|
1464
|
+
// resolve the union, enforce user-scope ownership, then delegate the
|
|
1465
|
+
// CANONICAL catalog name (the client's allowlist and the CMS row use
|
|
1466
|
+
// exact names; only the worker resolver is fuzzy).
|
|
1467
|
+
const canonicalName = await this._authorizePackageAgentCreate(agentName, owner ?? null, isAdmin ?? false);
|
|
1084
1468
|
const effectiveModel = await this.assertSessionModelCreatable({ model, owner });
|
|
1085
|
-
const session = await this.client.createSessionForAgent(
|
|
1469
|
+
const session = await this.client.createSessionForAgent(canonicalName, {
|
|
1086
1470
|
...(effectiveModel ? { model: effectiveModel } : {}),
|
|
1087
1471
|
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
1088
1472
|
...(contextTier ? { contextTier } : {}),
|
|
@@ -1103,9 +1487,6 @@ export class NodeSdkTransport {
|
|
|
1103
1487
|
};
|
|
1104
1488
|
}
|
|
1105
1489
|
|
|
1106
|
-
listCreatableAgents() {
|
|
1107
|
-
return [...this.creatableAgents];
|
|
1108
|
-
}
|
|
1109
1490
|
|
|
1110
1491
|
getSessionCreationPolicy() {
|
|
1111
1492
|
return this.sessionPolicy;
|
package/tui/src/plugin-config.js
CHANGED
|
@@ -176,6 +176,15 @@ export function resolvePortalConfigBundleFromPluginDirs(pluginDirs = []) {
|
|
|
176
176
|
loadingMessage: "Preparing your workspace",
|
|
177
177
|
loadingCopy: "Connecting the shared workspace and live session feeds...",
|
|
178
178
|
},
|
|
179
|
+
// Where "how do I build an agent package?" points. A layered
|
|
180
|
+
// deployment ships its OWN guide - the base instructions plus the
|
|
181
|
+
// skills, tools and MCP servers that exist only on that fleet - so
|
|
182
|
+
// this is config, not a constant. Public repo by default because the
|
|
183
|
+
// link is meant to be handed to a coding assistant, which cannot
|
|
184
|
+
// reach an intranet-only portal.
|
|
185
|
+
docs: {
|
|
186
|
+
agentPackageGuideUrl: "https://github.com/affandar/PilotSwarm/blob/main/docs/building-agent-packages.md",
|
|
187
|
+
},
|
|
179
188
|
auth: {
|
|
180
189
|
provider: null,
|
|
181
190
|
providers: {},
|
|
@@ -193,6 +202,7 @@ export function resolvePortalConfigBundleFromPluginDirs(pluginDirs = []) {
|
|
|
193
202
|
const portal = getObject(pluginMeta?.portal);
|
|
194
203
|
const portalBranding = getObject(portal.branding);
|
|
195
204
|
const portalUi = getObject(portal.ui);
|
|
205
|
+
const portalDocs = getObject(portal.docs);
|
|
196
206
|
const portalAuth = getObject(portal.auth);
|
|
197
207
|
const tui = getObject(pluginMeta?.tui);
|
|
198
208
|
|
|
@@ -250,6 +260,13 @@ export function resolvePortalConfigBundleFromPluginDirs(pluginDirs = []) {
|
|
|
250
260
|
loadingMessage: firstNonEmptyString(portalUi.loadingMessage, portal.loadingMessage, defaults.ui.loadingMessage) || defaults.ui.loadingMessage,
|
|
251
261
|
loadingCopy: firstNonEmptyString(portalUi.loadingCopy, portal.loadingCopy, defaults.ui.loadingCopy) || defaults.ui.loadingCopy,
|
|
252
262
|
},
|
|
263
|
+
docs: {
|
|
264
|
+
agentPackageGuideUrl: firstNonEmptyString(
|
|
265
|
+
portalDocs.agentPackageGuideUrl,
|
|
266
|
+
portal.agentPackageGuideUrl,
|
|
267
|
+
defaults.docs.agentPackageGuideUrl,
|
|
268
|
+
) || defaults.docs.agentPackageGuideUrl,
|
|
269
|
+
},
|
|
253
270
|
auth: {
|
|
254
271
|
provider: firstNonEmptyString(portalAuth.provider, portal.provider),
|
|
255
272
|
providers: getObject(portalAuth.providers),
|
package/ui/core/src/commands.js
CHANGED
|
@@ -89,6 +89,13 @@ export const UI_COMMANDS = {
|
|
|
89
89
|
OPEN_ADMIN_CONSOLE: "openAdminConsole",
|
|
90
90
|
CLOSE_ADMIN_CONSOLE: "closeAdminConsole",
|
|
91
91
|
ADMIN_REFRESH_PROFILE: "adminRefreshProfile",
|
|
92
|
+
ADMIN_SHOW_GHCP: "adminShowGhcp",
|
|
93
|
+
ADMIN_SHOW_PACKAGES: "adminShowPackages",
|
|
94
|
+
ADMIN_SHOW_WORKERS: "adminShowWorkers",
|
|
95
|
+
ADMIN_WORKERS_REFRESH: "adminWorkersRefresh",
|
|
96
|
+
ADMIN_PACKAGES_REFRESH: "adminPackagesRefresh",
|
|
97
|
+
ADMIN_PACKAGES_NEXT: "adminPackagesNext",
|
|
98
|
+
ADMIN_PACKAGES_PREV: "adminPackagesPrev",
|
|
92
99
|
ADMIN_BEGIN_EDIT_GHCP_KEY: "adminBeginEditGhcpKey",
|
|
93
100
|
ADMIN_CANCEL_EDIT_GHCP_KEY: "adminCancelEditGhcpKey",
|
|
94
101
|
ADMIN_SAVE_GHCP_KEY: "adminSaveGhcpKey",
|